Remember editor expansion state on rotates (E10)

* Prompt user about discarding changes when back pressed
  on editor (regressed in E5)
* Show a toast and close when no editors (i.e. input
  fields) can be bound.
* Try to clarify the different behavior of
  CompactSectionSectionView for names and groups.

Bug 23589603

Change-Id: If045ddb6d839574dc4109195b0d8841cd6083561
diff --git a/src/com/android/contacts/activities/ContactEditorBaseActivity.java b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
index 18ff30f..a86196b 100644
--- a/src/com/android/contacts/activities/ContactEditorBaseActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorBaseActivity.java
@@ -273,7 +273,7 @@
     @Override
     public void onBackPressed() {
         if (mFragment != null) {
-            mFragment.save(ContactEditor.SaveMode.CLOSE);
+            mFragment.revert();
         }
     }
 
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index d5209cb..e98a8de 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -40,6 +40,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
+import android.widget.Toast;
 
 import java.io.FileNotFoundException;
 
@@ -353,6 +354,17 @@
         rebindEditorsForNewContact(oldState, oldAccount, newAccount);
     }
 
+    @Override
+    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() {
         return (CompactRawContactsEditorView) mContent;
     }
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
index 25712d0..b506666 100644
--- a/src/com/android/contacts/editor/CompactKindSectionView.java
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Nickname;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -34,7 +33,6 @@
 import com.android.contacts.common.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.dataitem.DataKind;
-import com.android.contacts.editor.Editor.EditorListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -42,25 +40,21 @@
 /**
  * Version of {@link KindSectionView} that supports multiple RawContactDeltas.
  */
-public class CompactKindSectionView extends LinearLayout implements EditorListener {
+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.  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.
+     * marked as super primary.
      */
-    private static final class NameEditorListener implements Editor.EditorListener {
+    private static final class StructuredNameEditorListener implements Editor.EditorListener {
 
         private final ValuesDelta mValuesDelta;
         private final long mRawContactId;
         private final CompactRawContactsEditorView.Listener mListener;
 
-        public NameEditorListener(ValuesDelta valuesDelta, long rawContactId,
+        public StructuredNameEditorListener(ValuesDelta valuesDelta, long rawContactId,
                 CompactRawContactsEditorView.Listener listener) {
             mValuesDelta = valuesDelta;
             mRawContactId = rawContactId;
@@ -85,7 +79,11 @@
         }
     }
 
-    private static final class NicknameEditorListener implements Editor.EditorListener {
+    /**
+     * 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) {
@@ -97,16 +95,42 @@
         }
     }
 
+    /**
+     * 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 boolean mReadOnly;
     private ViewIdGenerator mViewIdGenerator;
     private CompactRawContactsEditorView.Listener mListener;
-    private String mMimeType;
 
     private boolean mShowOneEmptyEditor = false;
     private boolean mHideIfEmpty = true;
 
-    private LayoutInflater mInflater;
+    private LayoutInflater mLayoutInflater;
     private ViewGroup mEditors;
     private ImageView mIcon;
 
@@ -129,42 +153,22 @@
         }
     }
 
-    /** {@inheritDoc} */
     @Override
     protected void onFinishInflate() {
         setDrawingCacheEnabled(true);
         setAlwaysDrawnWithCacheEnabled(true);
 
-        mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mLayoutInflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
 
         mEditors = (ViewGroup) findViewById(R.id.kind_editors);
         mIcon = (ImageView) findViewById(R.id.kind_icon);
     }
 
-    @Override
-    public void onDeleteRequested(Editor editor) {
-        if (mShowOneEmptyEditor && 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();
-        } else {
-            editor.deleteEditor();
-        }
-    }
-
-    @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);
-        }
-    }
-
     /**
      * @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 has no effect
-     *         on name editors since the policy is to always show names.
+     *         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;
@@ -172,13 +176,14 @@
 
     /**
      * @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 has no effect
-     *         on name editors since the policy is to always show names.
+     *         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);
@@ -188,32 +193,39 @@
         }
     }
 
-    public void setState(List<KindSectionData> kindSectionDataList, boolean readOnly,
+    /**
+     * 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;
-        mReadOnly = readOnly;
         mViewIdGenerator = viewIdGenerator;
         mListener = listener;
 
-        // Set the icon using the first DataKind (all DataKinds should be the same type)
+        // Set the icon using the first DataKind
         final DataKind dataKind = mKindSectionDataList.isEmpty()
                 ? null : mKindSectionDataList.get(0).getDataKind();
         if (dataKind != null) {
-            mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0
-                    ? "" : getResources().getString(dataKind.titleRes));
             mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
                     dataKind.mimeType));
-            if (mIcon.getDrawable() == null) mIcon.setContentDescription(null);
-            mMimeType = dataKind.mimeType;
+            if (mIcon.getDrawable() != null) {
+                mIcon.setContentDescription(dataKind.titleRes == -1 || dataKind.titleRes == 0
+                        ? "" : getResources().getString(dataKind.titleRes));
+            }
         }
 
         rebuildFromState();
+
         updateEmptyEditors(/* shouldAnimate = */ false);
     }
 
-    /**
-     * Build editors for all current rows.
-     */
     private void rebuildFromState() {
         mEditors.removeAllViews();
 
@@ -221,29 +233,31 @@
             final String mimeType = kindSectionData.getDataKind().mimeType;
             if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
-                    createNameEditorViews(kindSectionData.getAccountType(),
+                    addNameEditorViews(kindSectionData.getAccountType(),
                             valuesDelta, kindSectionData.getRawContactDelta());
                 }
             } else if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                createGroupEditorView(kindSectionData.getRawContactDelta(),
+                addGroupEditorView(kindSectionData.getRawContactDelta(),
                         kindSectionData.getDataKind());
             } else {
+                final Editor.EditorListener editorListener = kindSectionData.isNicknameDataKind()
+                        ? new OtherNameKindEditorListener() : new NonNameEditorListener();
                 for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
-                    createEditorView(kindSectionData.getRawContactDelta(),
-                            kindSectionData.getDataKind(), valuesDelta);
+                    addNonNameEditorView(kindSectionData.getRawContactDelta(),
+                            kindSectionData.getDataKind(), valuesDelta, editorListener);
                 }
             }
         }
     }
 
-    private void createNameEditorViews(AccountType accountType,
+    private void addNameEditorViews(AccountType accountType,
             ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
         final boolean readOnly = !accountType.areContactsWritable();
 
         // Structured name
-        final StructuredNameEditorView nameView = (StructuredNameEditorView) mInflater.inflate(
-                R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false);
-        nameView.setEditorListener(new NameEditorListener(valuesDelta,
+        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(
@@ -251,15 +265,16 @@
                 valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
         if (readOnly) nameView.setAccountType(accountType);
 
-        // Correct start margin since there is another icon in the structured name layout
+        // 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
         if (readOnly) return;
 
-        final PhoneticNameEditorView phoneticNameView = (PhoneticNameEditorView) mInflater.inflate(
-                R.layout.phonetic_name_editor_view, mEditors, /* attachToRoot =*/ false);
+        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),
@@ -273,57 +288,40 @@
         mEditors.addView(phoneticNameView);
     }
 
-    private void createGroupEditorView(RawContactDelta rawContactDelta, DataKind dataKind) {
-        final GroupMembershipView view = (GroupMembershipView) mInflater.inflate(
+    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 another icon in the group layout
+        // 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);
     }
 
-    /**
-     * Creates an EditorView for the given values delta. This function must be used while
-     * constructing the views corresponding to the the object-model. The resulting EditorView is
-     * also added to the end of mEditors
-     */
-    private View createEditorView(RawContactDelta rawContactDelta, DataKind dataKind,
-            ValuesDelta valuesDelta) {
+    private View addNonNameEditorView(RawContactDelta rawContactDelta, DataKind dataKind,
+            ValuesDelta valuesDelta, Editor.EditorListener editorListener) {
         // Inflate the layout
-        final View view;
-        final int layoutResId = EditorUiUtils.getLayoutResourceId(dataKind.mimeType);
-        try {
-            view = mInflater.inflate(layoutResId, mEditors, false);
-        } catch (Exception e) {
-            throw new RuntimeException("Failed to allocate editor with layout resource ID " +
-                    layoutResId + " for mime type " + dataKind.mimeType + ": " + e.toString());
-        }
+        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);
         }
 
-        // Set whether the editor is enabled
-        view.setEnabled(isEnabled());
-
         if (view instanceof Editor) {
             final Editor editor = (Editor) view;
             editor.setDeletable(true);
-            // TODO: it's awkward to be doing something special for nicknames here
-            if (Nickname.CONTENT_ITEM_TYPE.equals(dataKind.mimeType)) {
-                editor.setEditorListener(new NicknameEditorListener());
-            } else {
-                editor.setEditorListener(this);
-            }
+            editor.setEditorListener(editorListener);
             editor.setValues(dataKind, valuesDelta, rawContactDelta, !dataKind.editable,
                     mViewIdGenerator);
         }
         mEditors.addView(view);
+
         return view;
     }
 
@@ -334,9 +332,43 @@
      * then the entire section is hidden.
      */
     public void updateEmptyEditors(boolean shouldAnimate) {
-        if (mKindSectionDataList.get(0).isNameDataKind()) {
+        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);
         }
     }
@@ -365,25 +397,17 @@
             } else {
                 // For phonetic names and nicknames, which can't be added, just show or hide them
                 if (mHideIfEmpty && editor.isEmpty()) {
-                    hideView(view, shouldAnimate);
+                    hideView(view);
                 } else {
-                    showView(view, shouldAnimate);
+                    showView(view, /* shouldAnimate =*/ false); // Animation here causes jank
                 }
             }
         }
     }
 
     private void updateEmptyNonNameEditors(boolean shouldAnimate) {
-        // Update whether the entire section is visible or not
-        final int editorCount = getEditorCount();
-        final List<View> emptyEditors = getEmptyEditors();
-        if (editorCount == emptyEditors.size() && mHideIfEmpty) {
-            setVisibility(GONE);
-            return;
-        }
-        setVisibility(VISIBLE);
-
         // 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;
@@ -403,29 +427,27 @@
         }
         // Determine if we should add a new empty editor
         final DataKind dataKind = mKindSectionDataList.get(0).getDataKind();
-        if (mReadOnly // We don't show empty editors for read only data kinds.
-                || dataKind == null // There is nothing we can do.
+        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 == editorCount && dataKind.typeOverallMax != 0)
+                || (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 RawContactDelta rawContactDelta =
+                    mKindSectionDataList.get(0).getRawContactDelta();
             final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
-            final View view = createEditorView(rawContactDelta, dataKind, values);
+            final View view = addNonNameEditorView(rawContactDelta, dataKind, values,
+                    new NonNameEditorListener());
             showView(view, shouldAnimate);
         }
     }
 
-    private void hideView(View view, boolean shouldAnimate) {
-        if (shouldAnimate) {
-            EditorAnimator.getInstance().hideEditorView(view);
-        } else {
-            view.setVisibility(View.GONE);
-        }
+    private void hideView(View view) {
+        view.setVisibility(View.GONE);
     }
 
     private void deleteView(View view, boolean shouldAnimate) {
@@ -440,35 +462,20 @@
     private void showView(View view, boolean shouldAnimate) {
         if (shouldAnimate) {
             view.setVisibility(View.GONE);
-            // TODO: still need this since we have animateLayoutChanges="true" on the parent layout?
             EditorAnimator.getInstance().showFieldFooter(view);
         } else {
             view.setVisibility(View.VISIBLE);
         }
     }
 
-    /**
-     * Returns a list of empty editor views in this section.
-     */
     private List<View> getEmptyEditors() {
-        List<View> emptyEditorViews = new ArrayList<View>();
+        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()) {
-                emptyEditorViews.add(view);
+                emptyEditors.add(view);
             }
         }
-        return emptyEditorViews;
-    }
-
-    private int getEditorCount() {
-        return mEditors.getChildCount();
-    }
-
-    /**
-     * Returns the mime type the kind being edited in this section.
-     */
-    public String getMimeType() {
-        return mMimeType;
+        return emptyEditors;
     }
 }
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 8ca6389..656f25f 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -34,6 +34,8 @@
 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;
@@ -102,6 +104,11 @@
          */
         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. */
@@ -130,7 +137,7 @@
      *     <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 palced at the end</li>
+     *     <li>Group membership is placed at the end</li>
      * </ol>
      */
     private static final class MimeTypeComparator implements Comparator<String> {
@@ -240,13 +247,43 @@
             }
 
             // The primary account name should be before all others
-            if (isRawContactDelta1Primary) return 1;
-            if (isRawContactDelta2Primary) return -1;
+            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;
@@ -277,6 +314,7 @@
     private Map<String,List<CompactKindSectionView>> mKindSectionViewsMap = new HashMap<>();
     private View mMoreFields;
 
+    private boolean mIsExpanded;
     private long mPhotoRawContactId;
 
     public CompactRawContactsEditorView(Context context) {
@@ -322,19 +360,7 @@
     @Override
     public void onClick(View view) {
         if (view.getId() == R.id.more_fields) {
-            // 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);
-                // Except the user is never allowed to add new names
-                final String mimeType = kindSectionView.getMimeType();
-                if (!StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    kindSectionView.setShowOneEmptyEditor(true);
-                }
-                kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ false);
-            }
-
+            showMoreFields();
             updateMoreFieldsButton();
         }
     }
@@ -348,6 +374,28 @@
         }
     }
 
+    @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();
+        }
+    }
+
     /**
      * Pass through to {@link CompactPhotoEditorView#setPhotoHandler}.
      */
@@ -398,6 +446,12 @@
         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,
@@ -423,11 +477,13 @@
         // 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 == null || mKindSectionDataMap.isEmpty()) {
+        if (mKindSectionDataMap.isEmpty()) {
             elog("No kind section data parsed from RawContactDelta(s)");
+            if (mListener != null) mListener.onBindEditorsFailed();
             return;
         }
 
@@ -437,6 +493,7 @@
         addAccountInfo();
         addPhotoView();
         addKindSectionViews();
+
         updateMoreFieldsButton();
     }
 
@@ -509,10 +566,9 @@
                         new KindSectionData(accountType, dataKind, rawContactDelta);
                 kindSectionDataList.add(kindSectionData);
 
-                // Note we must create a nickname entry on inserts
+                // Note we must create nickname entries
                 if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
-                        && kindSectionData.getValuesDeltas().isEmpty()
-                        && mHasNewContact) {
+                        && kindSectionData.getValuesDeltas().isEmpty()) {
                     RawContactModifier.insertChild(rawContactDelta, dataKind);
                 }
 
@@ -659,10 +715,6 @@
     private Pair<KindSectionData,ValuesDelta> getPrimaryKindSectionData(long id) {
         final String mimeType = Photo.CONTENT_ITEM_TYPE;
         final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
-        if (kindSectionDataList == null || kindSectionDataList.isEmpty()) {
-            wlog("photo: no kind section data parsed");
-            return null;
-        }
 
         KindSectionData resultKindSectionData = null;
         ValuesDelta resultValuesDelta = null;
@@ -779,12 +831,22 @@
             Collections.sort(kindSectionDataList, new EditorComparator(getContext()));
         }
 
-        kindSectionView.setState(kindSectionDataList, /* readOnly =*/ false, mViewIdGenerator,
-                mListener);
+        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++) {
diff --git a/src/com/android/contacts/editor/EditorIntents.java b/src/com/android/contacts/editor/EditorIntents.java
index ca8d5ae..26279df 100644
--- a/src/com/android/contacts/editor/EditorIntents.java
+++ b/src/com/android/contacts/editor/EditorIntents.java
@@ -110,7 +110,6 @@
      * Returns an Intent to start the fully expanded {@link ContactEditorActivity} for a
      * new contact.
      */
-    // TODO: Delete this if we don't need it to load the full editor for read only accounts
     public static Intent createInsertContactIntent(RawContactDeltaList rawContactDeltaList,
             String displayName, String phoneticName, boolean isNewLocalProfile) {
         final Intent intent = new Intent(ContactEditorBaseActivity.ACTION_INSERT,
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
index 7b383fb..d46001a 100644
--- a/src/com/android/contacts/editor/KindSectionData.java
+++ b/src/com/android/contacts/editor/KindSectionData.java
@@ -98,8 +98,11 @@
     }
 
     public boolean isNameDataKind() {
-        return StructuredName.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType)
-                || Nickname.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+        return StructuredName.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+    }
+
+    public boolean isNicknameDataKind() {
+        return Nickname.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
     }
 
     public RawContactDelta getRawContactDelta() {