am 192a01c6: Phonetic names must be displayed next to structured names (E7)

* commit '192a01c63c836a7a6ac3e75d8cc14e764f34a42b':
  Phonetic names must be displayed next to structured names (E7)
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