Phonetic names must be displayed next to structured names (E7)

Also keep phonetic name and nickname fields together with the
structured name editor field for the same account.

Bug 23589603

Change-Id: I3298d91dcd0451576374eed2401a768bec84b8e8
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
index 8959c18..50ef405 100644
--- a/src/com/android/contacts/editor/CompactKindSectionView.java
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -17,8 +17,9 @@
 package com.android.contacts.editor;
 
 import android.content.Context;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -35,8 +36,6 @@
 import com.android.contacts.editor.Editor.EditorListener;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -44,26 +43,6 @@
  */
 public class CompactKindSectionView extends LinearLayout implements EditorListener {
 
-    /** Sorts google account types before others. */
-    private static final class KindSectionComparator implements Comparator<KindSectionData> {
-
-        private RawContactDeltaComparator mRawContactDeltaComparator;
-
-        private KindSectionComparator(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;
-
-            return mRawContactDeltaComparator.compare(kindSectionData1.getRawContactDelta(),
-                    kindSectionData2.getRawContactDelta());
-        }
-    }
-
     /**
      * Marks a name as super primary when it is changed.
      *
@@ -74,7 +53,7 @@
      * Should only be set when a super primary name does not already exist since we only show
      * one name field.
      */
-    static final class NameEditorListener implements Editor.EditorListener {
+    private static final class NameEditorListener implements Editor.EditorListener {
 
         private final ValuesDelta mValuesDelta;
         private final long mRawContactId;
@@ -101,13 +80,23 @@
 
         @Override
         public void onDeleteRequested(Editor editor) {
+            editor.clearAllFields();
         }
     }
 
-    private ViewGroup mEditors;
-    private ImageView mIcon;
+    private static final class NicknameEditorListener implements Editor.EditorListener {
 
-    private List<KindSectionData> mKindSectionDatas;
+        @Override
+        public void onRequest(int request) {
+        }
+
+        @Override
+        public void onDeleteRequested(Editor editor) {
+            editor.clearAllFields();
+        }
+    }
+
+    private List<KindSectionData> mKindSectionDataList;
     private boolean mReadOnly;
     private ViewIdGenerator mViewIdGenerator;
     private CompactRawContactsEditorView.Listener mListener;
@@ -117,6 +106,8 @@
     private boolean mHideIfEmpty = true;
 
     private LayoutInflater mInflater;
+    private ViewGroup mEditors;
+    private ImageView mIcon;
 
     public CompactKindSectionView(Context context) {
         this(context, /* attrs =*/ null);
@@ -135,8 +126,6 @@
                 mEditors.getChildAt(i).setEnabled(enabled);
             }
         }
-        // TODO: why is this necessary?
-        updateEmptyEditors(/* shouldAnimate = */ true);
     }
 
     /** {@inheritDoc} */
@@ -172,8 +161,9 @@
     }
 
     /**
-     * @param showOneEmptyEditor If true, we will always show one empty, otherwise an empty editor
-     *         will not be shown until the user enters a value.
+     * @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.
      */
     public void setShowOneEmptyEditor(boolean showOneEmptyEditor) {
         mShowOneEmptyEditor = showOneEmptyEditor;
@@ -181,23 +171,23 @@
 
     /**
      * @param hideWhenEmpty If true, the entire section will be hidden if all inputs are empty,
-     *         otherwise one empty input will always be displayed.
+     *         otherwise one empty input will always be displayed.  Note, this has no effect
+     *         on name editors since the policy is to always show names.
      */
     public void setHideWhenEmpty(boolean hideWhenEmpty) {
         mHideIfEmpty = hideWhenEmpty;
     }
 
-    public void setState(List<KindSectionData> kindSectionDatas, boolean readOnly,
+    public void setState(List<KindSectionData> kindSectionDataList, boolean readOnly,
             ViewIdGenerator viewIdGenerator, CompactRawContactsEditorView.Listener listener) {
-        mKindSectionDatas = kindSectionDatas;
-        Collections.sort(mKindSectionDatas, new KindSectionComparator(getContext()));
+        mKindSectionDataList = kindSectionDataList;
         mReadOnly = readOnly;
         mViewIdGenerator = viewIdGenerator;
         mListener = listener;
 
         // Set the icon using the first DataKind (all DataKinds should be the same type)
-        final DataKind dataKind = mKindSectionDatas.isEmpty()
-                ? null : mKindSectionDatas.get(0).getDataKind();
+        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));
@@ -217,20 +207,10 @@
     private void rebuildFromState() {
         mEditors.removeAllViews();
 
-        // Check if we are displaying anything here
-        boolean hasValuesDeltas = false;
-        for (KindSectionData kindSectionData : mKindSectionDatas) {
-            if (kindSectionData.hasValuesDeltas()) {
-                hasValuesDeltas = true;
-                break;
-            }
-        }
-        if (!hasValuesDeltas) return;
-
-        for (KindSectionData kindSectionData : mKindSectionDatas) {
+        for (KindSectionData kindSectionData : mKindSectionDataList) {
             if (StructuredName.CONTENT_ITEM_TYPE.equals(kindSectionData.getDataKind().mimeType)) {
                 for (ValuesDelta valuesDelta : kindSectionData.getValuesDeltas()) {
-                    createStructuredNameEditorView(kindSectionData.getAccountType(),
+                    createNameEditorViews(kindSectionData.getAccountType(),
                             valuesDelta, kindSectionData.getRawContactDelta());
                 }
             } else {
@@ -242,21 +222,41 @@
         }
     }
 
-    private void createStructuredNameEditorView(AccountType accountType,
+    private void createNameEditorViews(AccountType accountType,
             ValuesDelta valuesDelta, RawContactDelta rawContactDelta) {
-        final StructuredNameEditorView view = (StructuredNameEditorView) mInflater.inflate(
-                R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false);
-        view.setEditorListener(new NameEditorListener(valuesDelta,
-                rawContactDelta.getRawContactId(), mListener));
-        view.findViewById(R.id.kind_icon).setVisibility(View.GONE);
-        view.setDeletable(false);
         final boolean readOnly = !accountType.areContactsWritable();
-        view.setValues(accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
+
+        // Structured name
+        final StructuredNameEditorView nameView = (StructuredNameEditorView) mInflater.inflate(
+                R.layout.structured_name_editor_view, mEditors, /* attachToRoot =*/ false);
+        nameView.setEditorListener(new NameEditorListener(valuesDelta,
+                rawContactDelta.getRawContactId(), mListener));
+        nameView.setDeletable(false);
+        nameView.setValues(
+                accountType.getKindForMimetype(DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME),
                 valuesDelta, rawContactDelta, readOnly, mViewIdGenerator);
-        if (readOnly) {
-            view.setAccountType(accountType);
-        }
-        mEditors.addView(view);
+        if (readOnly) nameView.setAccountType(accountType);
+
+        // Correct start margin since there is another 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);
+        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);
     }
 
     /**
@@ -281,21 +281,18 @@
             ((LabeledEditorView) view).setHideTypeInitially(true);
         }
 
-        // Fix the start margin for phonetic name views
-        if (view instanceof PhoneticNameEditorView) {
-            final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
-                    LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
-            layoutParams.setMargins(0, 0, 0, 0);
-            view.setLayoutParams(layoutParams);
-        }
-
         // Set whether the editor is enabled
         view.setEnabled(isEnabled());
 
         if (view instanceof Editor) {
             final Editor editor = (Editor) view;
             editor.setDeletable(true);
-            editor.setEditorListener(this);
+            // 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.setValues(dataKind, valuesDelta, rawContactDelta, !dataKind.editable,
                     mViewIdGenerator);
         }
@@ -310,10 +307,46 @@
      * then the entire section is hidden.
      */
     public void updateEmptyEditors(boolean shouldAnimate) {
-        if (mKindSectionDatas.isEmpty()) return;
-        final DataKind dataKind = mKindSectionDatas.get(0).getDataKind();
-        final RawContactDelta rawContactDelta = mKindSectionDatas.get(0).getRawContactDelta();
+        if (mKindSectionDataList.get(0).isNameDataKind()) {
+            updateEmptyNameEditors(shouldAnimate);
+        } else {
+            updateEmptyNonNameEditors(shouldAnimate);
+        }
+    }
 
+    private void updateEmptyNameEditors(boolean shouldAnimate) {
+        boolean isEmptyNameEditorVisible = false;
+
+        for (int i = 0; i < mEditors.getChildCount(); i++) {
+            final View view = mEditors.getChildAt(i);
+            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, shouldAnimate);
+                } else {
+                    showView(view, shouldAnimate);
+                }
+            }
+        }
+    }
+
+    private void updateEmptyNonNameEditors(boolean shouldAnimate) {
         // Update whether the entire section is visible or not
         final int editorCount = getEditorCount();
         final List<View> emptyEditors = getEmptyEditors();
@@ -323,36 +356,28 @@
         }
         setVisibility(VISIBLE);
 
-        // Update the number of empty editors
+        // Prune excess empty editors
         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 emptyEditorView : emptyEditors) {
+            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.
-
-                // TODO: we must not delete the editor for the "primary" account. It's working
-                // because the primary account is always the last one when the account is changed
-                // in the editor but it is a bit brittle to rely on that (though that is what is
-                // happening in LMP).
-                if (emptyEditorView.findFocus() == null) {
-                    final Editor editor = (Editor) emptyEditorView;
-                    if (shouldAnimate) {
-                        editor.deleteEditor();
-                    } else {
-                        mEditors.removeView(emptyEditorView);
-                    }
+                // 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;
+                    if (deleted == emptyEditors.size() - 1) break;
                 }
             }
             return;
         }
-        if (dataKind == null // There is nothing we can do.
-                || mReadOnly // We don't show empty editors for read only data kinds.
+        // 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.
                 // We have already reached the maximum number of editors, don't add any more.
                 || (dataKind.typeOverallMax == editorCount && dataKind.typeOverallMax != 0)
                 // We have already reached the maximum number of empty editors, don't add any more.
@@ -361,12 +386,37 @@
         }
         // Add a new empty editor
         if (mShowOneEmptyEditor) {
+            final RawContactDelta rawContactDelta = mKindSectionDataList.get(0).getRawContactDelta();
             final ValuesDelta values = RawContactModifier.insertChild(rawContactDelta, dataKind);
-            final View editorView = createEditorView(rawContactDelta, dataKind, values);
-            if (shouldAnimate) {
-                editorView.setVisibility(View.GONE);
-                EditorAnimator.getInstance().showFieldFooter(editorView);
-            }
+            final View view = createEditorView(rawContactDelta, dataKind, values);
+            showView(view, shouldAnimate);
+        }
+    }
+
+    private void hideView(View view, boolean shouldAnimate) {
+        if (shouldAnimate) {
+            EditorAnimator.getInstance().hideEditorView(view);
+        } else {
+            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);
+            // TODO: still need this since we have animateLayoutChanges="true" on the parent layout?
+            EditorAnimator.getInstance().showFieldFooter(view);
+        } else {
+            view.setVisibility(View.VISIBLE);
         }
     }
 
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 8f268b4..540bf1a 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -61,6 +61,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -75,7 +76,8 @@
 
     private static final String TAG = "CompactEditorView";
 
-    private static final MimeTypeComparator MIME_TYPE_COMPARATOR = new MimeTypeComparator();
+    private static final KindSectionDataMapEntryComparator
+            KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR = new KindSectionDataMapEntryComparator();
 
     /**
      * Callbacks for hosts of {@link CompactRawContactsEditorView}s.
@@ -101,19 +103,38 @@
                 AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
     }
 
-    /** Used to sort kind sections. */
-    private static final class MimeTypeComparator implements
+    /** Used to sort entire kind sections. */
+    private static final class KindSectionDataMapEntryComparator implements
             Comparator<Map.Entry<String,List<KindSectionData>>> {
 
-        // Order of kinds roughly matches quick contacts; we diverge in the following ways:
-        // 1) all names are together at the top (in quick contacts the nickname and phonetic name
-        //    are in the about card)
-        // 2) IM is moved up after addresses
-        // 3) SIP addresses are moved to below phone numbers
+        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);
+        }
+    }
+
+    /**
+     * 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>
+     * </ol>
+     */
+    private static final class MimeTypeComparator implements Comparator<String> {
+
         private static final List<String> MIME_TYPE_ORDER = Arrays.asList(new String[] {
                 StructuredName.CONTENT_ITEM_TYPE,
-                DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME,
-                DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME,
                 Nickname.CONTENT_ITEM_TYPE,
                 Phone.CONTENT_ITEM_TYPE,
                 SipAddress.CONTENT_ITEM_TYPE,
@@ -128,14 +149,10 @@
         });
 
         @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();
+        public int compare(String mimeType1, String mimeType2) {
+            if (mimeType1 == mimeType2) return 0;
+            if (mimeType1 == null) return -1;
+            if (mimeType2 == null) return 1;
 
             int index1 = MIME_TYPE_ORDER.indexOf(mimeType1);
             int index2 = MIME_TYPE_ORDER.indexOf(mimeType2);
@@ -149,6 +166,84 @@
         }
     }
 
+    /**
+     * 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);
+        }
+    }
+
     private CompactRawContactsEditorView.Listener mListener;
 
     private AccountTypeManager mAccountTypeManager;
@@ -228,8 +323,9 @@
                 final CompactKindSectionView kindSectionView =
                         (CompactKindSectionView) mKindSectionViews.getChildAt(i);
                 kindSectionView.setHideWhenEmpty(false);
-                // Prevent the user from adding new names
-                if (!StructuredName.CONTENT_ITEM_TYPE.equals(kindSectionView.getMimeType())) {
+                // 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);
@@ -284,14 +380,11 @@
     }
 
     public View getAggregationAnchorView() {
-        // TODO: since there may be more than one structured name now we should return the one
-        // being edited instead of just the first one.
-        for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
-            final CompactKindSectionView kindSectionView =
-                    (CompactKindSectionView) mKindSectionViews.getChildAt(i);
-            if (!StructuredName.CONTENT_ITEM_TYPE.equals(kindSectionView.getMimeType())) {
-                return kindSectionView.findViewById(R.id.anchor_view);
-            }
+        // The kind section for the primary account is sorted to the front
+        final List<KindSectionData> kindSectionDataList = getKindSectionDataList(
+                StructuredName.CONTENT_ITEM_TYPE);
+        if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
+            return mKindSectionViews.getChildAt(0).findViewById(R.id.anchor_view);
         }
         return null;
     }
@@ -321,8 +414,7 @@
             elog("No raw contact deltas");
             return;
         }
-        vlog("state: setting state from " + rawContactDeltas.size() + " RawContactDelta(s)");
-        parseRawContactDeltas(rawContactDeltas);
+        parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
         if (mKindSectionDataMap == null || mKindSectionDataMap.isEmpty()) {
             elog("No kind section data parsed from RawContactDelta(s)");
             return;
@@ -337,14 +429,15 @@
         updateMoreFieldsButton();
     }
 
-    private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
-        if (mPrimaryAccount != null) {
+    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()) continue;
-                if (matchesPrimaryAccount(rawContactDelta)) {
+                if (matchesAccount(primaryAccount, rawContactDelta)) {
                     vlog("parse: matched primary account raw contact");
                     mPrimaryRawContactDelta = rawContactDelta;
                     break;
@@ -363,20 +456,22 @@
                 }
             }
         }
+
         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
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
+        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;
-            vlog("parse: " + rawContactDelta);
             final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
             if (accountType == null) continue;
             final List<DataKind> dataKinds = accountType.getSortedDataKinds();
@@ -384,20 +479,42 @@
             vlog("parse: " + dataKindSize + " dataKinds(s)");
             for (int i = 0; i < dataKindSize; i++) {
                 final DataKind dataKind = dataKinds.get(i);
-                if (dataKind == null || !dataKind.editable) continue;
+                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(dataKind.mimeType);
+                        getKindSectionDataList(mimeType);
                 final KindSectionData kindSectionData =
                         new KindSectionData(accountType, dataKind, rawContactDelta);
                 kindSectionDataList.add(kindSectionData);
+
+                // Note we must create a nickname entry on inserts
+                if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)
+                        && kindSectionData.getValuesDeltas().isEmpty()
+                        && mHasNewContact) {
+                    RawContactModifier.insertChild(rawContactDelta, dataKind);
+                }
+
                 vlog("parse: " + i + " " + dataKind.mimeType + " " +
-                        (kindSectionData.hasValuesDeltas()
-                                ? kindSectionData.getValuesDeltas().size() : 0) + " value(s)");
+                        kindSectionData.getValuesDeltas().size() + " value(s)");
             }
         }
     }
 
     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<>();
@@ -406,11 +523,13 @@
         return kindSectionDataList;
     }
 
-    /** Whether the given RawContactDelta is from the primary account. */
-    private boolean matchesPrimaryAccount(RawContactDelta rawContactDelta) {
-        return Objects.equals(mPrimaryAccount.name, rawContactDelta.getAccountName())
-                && Objects.equals(mPrimaryAccount.type, rawContactDelta.getAccountType())
-                && Objects.equals(mPrimaryAccount.dataSet, rawContactDelta.getDataSet());
+    /** 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() {
@@ -513,7 +632,7 @@
 
         // 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. The new raw contact is the first writable one.
+        // that was created.
         if (mHasNewContact) {
             mPhotoRawContactId = mPrimaryRawContactDelta == null
                     ? null : mPrimaryRawContactDelta.getRawContactId();
@@ -584,7 +703,7 @@
     private void addKindSectionViews() {
         // Sort the kinds
         final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
-                new TreeSet<>(MIME_TYPE_COMPARATOR);
+                new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
         entries.addAll(mKindSectionDataMap.entrySet());
 
         vlog("kind: " + entries.size() + " kindSection(s)");
@@ -602,13 +721,12 @@
             }
 
             // Ignore mime types that we don't handle
-            if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)
-                    || DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME.equals(mimeType)) {
+            if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
                 vlog("kind: " + i + " " + mimeType + " dropped");
                 continue;
             }
 
-            if (kindSectionDataList != null) {
+            if (kindSectionDataList != null && !kindSectionDataList.isEmpty()) {
                 vlog("kind: " + i + " " + mimeType + ": " + kindSectionDataList.size() +
                         " kindSectionData(s)");
 
@@ -625,18 +743,23 @@
                 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
                         /* attachToRoot =*/ false);
 
-        if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
-                || Phone.CONTENT_ITEM_TYPE.equals(mimeType)
+        if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
                 || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
-            // Names, phone numbers, and email addresses are always displayed.
-            // Phone numbers and email addresses are the only types you add new values
-            // to initially.
+            // Phone numbers and email addresses are always displayed,
+            // even if they are empty
             kindSectionView.setHideWhenEmpty(false);
         }
 
-        // Prevent the user from adding any new names
-        if (!StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
-            kindSectionView.setShowOneEmptyEditor(true);
+        // 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, /* readOnly =*/ false, mViewIdGenerator,
diff --git a/src/com/android/contacts/editor/KindSectionData.java b/src/com/android/contacts/editor/KindSectionData.java
index 6455ff5..7b383fb 100644
--- a/src/com/android/contacts/editor/KindSectionData.java
+++ b/src/com/android/contacts/editor/KindSectionData.java
@@ -22,10 +22,10 @@
 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.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -44,23 +44,13 @@
         mAccountType = accountType;
         mDataKind = dataKind;
         mRawContactDelta = rawContactDelta;
-
-        // Note that for phonetic names we use the structured name mime type to look up values
-        final String mimeType = DataKind.PSEUDO_MIME_TYPE_PHONETIC_NAME.equals(dataKind.mimeType)
-                ? StructuredName.CONTENT_ITEM_TYPE : dataKind.mimeType;
-        mValuesDeltas = mRawContactDelta.hasMimeEntries(mimeType)
-                ? mRawContactDelta.getMimeEntries(mimeType)
-                : Collections.EMPTY_LIST;
+        mValuesDeltas = mRawContactDelta.getMimeEntries(dataKind.mimeType, /* lazyCreate= */ true);
     }
 
     public AccountType getAccountType() {
         return mAccountType;
     }
 
-    public boolean hasValuesDeltas() {
-        return !mValuesDeltas.isEmpty();
-    }
-
     public List<ValuesDelta> getValuesDeltas() {
         return mValuesDeltas;
     }
@@ -107,6 +97,11 @@
         return mDataKind;
     }
 
+    public boolean isNameDataKind() {
+        return StructuredName.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType)
+                || Nickname.CONTENT_ITEM_TYPE.equals(mDataKind.mimeType);
+    }
+
     public RawContactDelta getRawContactDelta() {
         return mRawContactDelta;
     }
@@ -116,6 +111,6 @@
                 KindSectionData.class.getSimpleName(),
                 mAccountType.accountType,
                 mAccountType.dataSet,
-                hasValuesDeltas() ? getValuesDeltas().size() : "null");
+                getValuesDeltas().size());
     }
 }
\ No newline at end of file