Move finding primary/writable raw contacts to KindSectionDataList (E16)

This will help us since editing the 1) name and 2) photo, and 3) finding
the primary account used to sort editors so that new fields get added
to the primary account all do something similar to find the
ValuesDelta that changes are written too.

We can also enforce that only the same DataKind/mime-type are added
to a KindSectionDataList and passed to a KindSectionView.

Bug 24509375
Bug 23589603

Change-Id: I2fc603c3cea2fb7f10a96c8592688ccf3deef56a
diff --git a/src/com/android/contacts/editor/CompactKindSectionView.java b/src/com/android/contacts/editor/CompactKindSectionView.java
index 448e7c5..91e5a6d 100644
--- a/src/com/android/contacts/editor/CompactKindSectionView.java
+++ b/src/com/android/contacts/editor/CompactKindSectionView.java
@@ -145,7 +145,7 @@
         }
     }
 
-    private List<KindSectionData> mKindSectionDataList;
+    private KindSectionDataList mKindSectionDataList;
     private ViewIdGenerator mViewIdGenerator;
     private CompactRawContactsEditorView.Listener mListener;
 
@@ -225,15 +225,14 @@
      * 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,
+    public void setState(KindSectionDataList kindSectionDataList,
             ViewIdGenerator viewIdGenerator, CompactRawContactsEditorView.Listener listener) {
         mKindSectionDataList = kindSectionDataList;
         mViewIdGenerator = viewIdGenerator;
         mListener = listener;
 
         // Set the icon using the first DataKind
-        final DataKind dataKind = mKindSectionDataList.isEmpty()
-                ? null : mKindSectionDataList.get(0).getDataKind();
+        final DataKind dataKind = mKindSectionDataList.getDataKind();
         if (dataKind != null) {
             mIcon.setImageDrawable(EditorUiUtils.getMimeTypeDrawable(getContext(),
                     dataKind.mimeType));
@@ -379,9 +378,10 @@
      * then the entire section is hidden.
      */
     public void updateEmptyEditors(boolean shouldAnimate) {
-        final boolean isNameKindSection = mKindSectionDataList.get(0).isNameDataKind();
+        final boolean isNameKindSection = StructuredName.CONTENT_ITEM_TYPE.equals(
+                mKindSectionDataList.getMimeType());
         final boolean isGroupKindSection = GroupMembership.CONTENT_ITEM_TYPE.equals(
-                mKindSectionDataList.get(0).getDataKind().mimeType);
+                mKindSectionDataList.getMimeType());
 
         if (isNameKindSection) {
             // The name kind section is always visible
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index f808d4d..40574da 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -17,7 +17,6 @@
 package com.android.contacts.editor;
 
 import com.android.contacts.R;
-import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.common.model.RawContactDelta;
 import com.android.contacts.common.model.RawContactDeltaList;
@@ -28,7 +27,6 @@
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.AccountsListAdapter;
 import com.android.contacts.common.util.MaterialColorMapUtils;
-import com.android.contacts.util.ContactPhotoUtils;
 import com.android.contacts.util.UiClosables;
 
 import android.content.ContentUris;
@@ -69,7 +67,6 @@
 import android.widget.ListPopupWindow;
 import android.widget.TextView;
 
-import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -79,7 +76,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 
@@ -193,13 +189,13 @@
 
     /** Used to sort entire kind sections. */
     private static final class KindSectionDataMapEntryComparator implements
-            Comparator<Map.Entry<String,List<KindSectionData>>> {
+            Comparator<Map.Entry<String,KindSectionDataList>> {
 
         final MimeTypeComparator mMimeTypeComparator = new MimeTypeComparator();
 
         @Override
-        public int compare(Map.Entry<String, List<KindSectionData>> entry1,
-                Map.Entry<String, List<KindSectionData>> entry2) {
+        public int compare(Map.Entry<String, KindSectionDataList> entry1,
+                Map.Entry<String, KindSectionDataList> entry2) {
             if (entry1 == entry2) return 0;
             if (entry1 == null) return -1;
             if (entry2 == null) return 1;
@@ -376,7 +372,7 @@
     private boolean mIsUserProfile;
     private AccountWithDataSet mPrimaryAccount;
     private RawContactDelta mPrimaryRawContactDelta;
-    private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
+    private Map<String,KindSectionDataList> mKindSectionDataMap = new HashMap<>();
 
     // Account header
     private View mAccountHeaderContainer;
@@ -400,6 +396,7 @@
     private View mMoreFields;
 
     private boolean mIsExpanded;
+
     private long mPhotoRawContactId;
     private ValuesDelta mPhotoValuesDelta;
 
@@ -660,12 +657,22 @@
             if (mListener != null) mListener.onBindEditorsFailed();
             return;
         }
-        parseRawContactDeltas(rawContactDeltas, mPrimaryAccount);
+        parseRawContactDeltas(rawContactDeltas);
         if (mKindSectionDataMap.isEmpty()) {
             elog("No kind section data parsed from RawContactDelta(s)");
             if (mListener != null) mListener.onBindEditorsFailed();
             return;
         }
+        mPrimaryRawContactDelta = mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE)
+                .getEntryToWrite(mPrimaryAccount, mHasNewContact).first.getRawContactDelta();
+        if (mPrimaryRawContactDelta != null) {
+            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+                    StructuredName.CONTENT_ITEM_TYPE);
+            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+                    Photo.CONTENT_ITEM_TYPE);
+        }
 
         // Setup the view
         addAccountInfo(rawContactDeltas);
@@ -679,43 +686,7 @@
         if (mListener != null) mListener.onEditorsBound();
     }
 
-    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 (matchesAccount(primaryAccount, rawContactDelta)) {
-                    vlog("parse: matched primary account raw contact");
-                    mPrimaryRawContactDelta = rawContactDelta;
-                    break;
-                }
-            }
-        }
-        if (mPrimaryRawContactDelta == null) {
-            // Fall back to the first writable raw contact
-            for (RawContactDelta rawContactDelta : rawContactDeltas) {
-                if (!rawContactDelta.isVisible()) continue;
-                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-                if (accountType != null && accountType.areContactsWritable()) {
-                    vlog("parse: falling back to the first writable raw contact as primary");
-                    mPrimaryRawContactDelta = rawContactDelta;
-                    break;
-                }
-            }
-        }
-
-        if (mPrimaryRawContactDelta != null) {
-            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
-                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
-                    StructuredName.CONTENT_ITEM_TYPE);
-            RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
-                    mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
-                    Photo.CONTENT_ITEM_TYPE);
-        }
-
+    private void parseRawContactDeltas(RawContactDeltaList rawContactDeltas) {
         // Build the kind section data list map
         vlog("parse: " + rawContactDeltas.size() + " rawContactDelta(s)");
         for (int j = 0; j < rawContactDeltas.size(); j++) {
@@ -743,7 +714,7 @@
                 }
 
                 final List<KindSectionData> kindSectionDataList =
-                        getKindSectionDataList(mimeType);
+                        getOrCreateKindSectionDataList(mimeType);
                 final KindSectionData kindSectionData =
                         new KindSectionData(accountType, dataKind, rawContactDelta);
                 kindSectionDataList.add(kindSectionData);
@@ -760,27 +731,18 @@
         }
     }
 
-    private List<KindSectionData> getKindSectionDataList(String mimeType) {
+    private List<KindSectionData> getOrCreateKindSectionDataList(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);
+        KindSectionDataList kindSectionDataList = mKindSectionDataMap.get(mimeType);
         if (kindSectionDataList == null) {
-            kindSectionDataList = new ArrayList<>();
+            kindSectionDataList = new KindSectionDataList();
             mKindSectionDataMap.put(mimeType, kindSectionDataList);
         }
         return kindSectionDataList;
     }
 
-    /** 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(RawContactDeltaList rawContactDeltas) {
         if (mPrimaryRawContactDelta == null) {
             mAccountHeaderContainer.setVisibility(View.GONE);
@@ -961,123 +923,44 @@
 
     private void addPhotoView() {
         // Get the kind section data and values delta that we will display in the photo view
-        Pair<KindSectionData,ValuesDelta> pair = getPrimaryPhotoKindSectionData(mPhotoId);
-        if (pair == null) {
+        final KindSectionDataList kindSectionDataList =
+                mKindSectionDataMap.get(Photo.CONTENT_ITEM_TYPE);
+        final Pair<KindSectionData,ValuesDelta> photoToDisplay =
+                kindSectionDataList.getEntryToDisplay(mPhotoId);
+        if (photoToDisplay == null) {
             wlog("photo: no kind section data parsed");
-            mPhotoView.setReadOnly(true);
+            mPhotoView.setVisibility(View.GONE);
             return;
         }
 
         // Set the photo view
-        final ValuesDelta primaryValuesDelta = pair.second;
-        mPhotoView.setPhoto(primaryValuesDelta, mMaterialPalette);
+        mPhotoView.setPhoto(photoToDisplay.second, mMaterialPalette);
 
         // Find the raw contact ID and values delta that will be written when the photo is edited
-        final KindSectionData primaryKindSectionData = pair.first;
-        if (mHasNewContact && mPrimaryRawContactDelta != null
-                && !primaryKindSectionData.getValuesDeltas().isEmpty()) {
-            // If we're editing a read-only contact we want to display the photo from the
-            // read-only contact in a photo editor view, but update the new raw contact
-            // that was created.
-            mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
-            mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
-            mPhotoView.setReadOnly(false);
-            return;
-        }
-        if (primaryKindSectionData.getAccountType().areContactsWritable() &&
-                !primaryKindSectionData.getValuesDeltas().isEmpty()) {
-            mPhotoRawContactId = primaryKindSectionData.getRawContactDelta().getRawContactId();
-            mPhotoValuesDelta = primaryKindSectionData.getValuesDeltas().get(0);
-            mPhotoView.setReadOnly(false);
-            return;
-        }
-
-        final KindSectionData writableKindSectionData = getFirstWritablePhotoKindSectionData();
-        if (writableKindSectionData == null
-                || writableKindSectionData.getValuesDeltas().isEmpty()) {
+        final Pair<KindSectionData,ValuesDelta> photoToWrite = kindSectionDataList.getEntryToWrite(
+                mPrimaryAccount, mHasNewContact);
+        if (photoToWrite == null) {
             mPhotoView.setReadOnly(true);
             return;
         }
-        mPhotoRawContactId = writableKindSectionData.getRawContactDelta().getRawContactId();
-        mPhotoValuesDelta = writableKindSectionData.getValuesDeltas().get(0);
         mPhotoView.setReadOnly(false);
-    }
-
-    private Pair<KindSectionData,ValuesDelta> getPrimaryPhotoKindSectionData(long id) {
-        final String mimeType = Photo.CONTENT_ITEM_TYPE;
-        final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
-
-        KindSectionData resultKindSectionData = null;
-        ValuesDelta resultValuesDelta = null;
-        if (id > 0) {
-            // Look for a match for the ID that was passed in
-            for (KindSectionData kindSectionData : kindSectionDataList) {
-                resultValuesDelta = kindSectionData.getValuesDeltaById(id);
-                if (resultValuesDelta != null) {
-                    vlog("photo: matched kind section data by ID");
-                    resultKindSectionData = kindSectionData;
-                    break;
-                }
-            }
-        }
-        if (resultKindSectionData == null) {
-            // Look for a super primary photo
-            for (KindSectionData kindSectionData : kindSectionDataList) {
-                resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
-                if (resultValuesDelta != null) {
-                    wlog("photo: matched super primary kind section data");
-                    resultKindSectionData = kindSectionData;
-                    break;
-                }
-            }
-        }
-        if (resultKindSectionData == null) {
-            // Fall back to the first non-empty value
-            for (KindSectionData kindSectionData : kindSectionDataList) {
-                resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
-                if (resultValuesDelta != null) {
-                    vlog("photo: using first non empty value");
-                    resultKindSectionData = kindSectionData;
-                    break;
-                }
-            }
-        }
-        if (resultKindSectionData == null || resultValuesDelta == null) {
-            final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
-            if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
-                vlog("photo: falling back to first empty entry");
-                resultValuesDelta = valuesDeltaList.get(0);
-                resultKindSectionData = kindSectionDataList.get(0);
-            }
-        }
-        return resultKindSectionData != null && resultValuesDelta != null
-                ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
-    }
-
-    private KindSectionData getFirstWritablePhotoKindSectionData() {
-        final String mimeType = Photo.CONTENT_ITEM_TYPE;
-        final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
-        for (KindSectionData kindSectionData : kindSectionDataList) {
-            if (kindSectionData.getAccountType().areContactsWritable()) {
-                return kindSectionData;
-            }
-        }
-        return null;
+        mPhotoRawContactId = photoToWrite.first.getRawContactDelta().getRawContactId();
+        mPhotoValuesDelta = photoToWrite.second;
     }
 
     private void addKindSectionViews() {
         // Sort the kinds
-        final TreeSet<Map.Entry<String,List<KindSectionData>>> entries =
+        final TreeSet<Map.Entry<String,KindSectionDataList>> entries =
                 new TreeSet<>(KIND_SECTION_DATA_MAP_ENTRY_COMPARATOR);
         entries.addAll(mKindSectionDataMap.entrySet());
 
         vlog("kind: " + entries.size() + " kindSection(s)");
         int i = -1;
-        for (Map.Entry<String, List<KindSectionData>> entry : entries) {
+        for (Map.Entry<String, KindSectionDataList> entry : entries) {
             i++;
 
             final String mimeType = entry.getKey();
-            final List<KindSectionData> kindSectionDataList = entry.getValue();
+            final KindSectionDataList kindSectionDataList = entry.getValue();
 
             // Ignore mime types that we've already handled
             if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
@@ -1109,7 +992,7 @@
     }
 
     private CompactKindSectionView inflateKindSectionView(ViewGroup viewGroup,
-            List<KindSectionData> kindSectionDataList, String mimeType) {
+            KindSectionDataList kindSectionDataList, String mimeType) {
         final CompactKindSectionView kindSectionView = (CompactKindSectionView)
                 mLayoutInflater.inflate(R.layout.compact_item_kind_section, viewGroup,
                         /* attachToRoot =*/ false);
diff --git a/src/com/android/contacts/editor/KindSectionDataList.java b/src/com/android/contacts/editor/KindSectionDataList.java
new file mode 100644
index 0000000..180e8b2
--- /dev/null
+++ b/src/com/android/contacts/editor/KindSectionDataList.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.editor;
+
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.dataitem.DataKind;
+
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Container for multiple {@link KindSectionData} objects.  Provides convenience methods for
+ * interrogating the collection for a certain KindSectionData item (e.g. the first writable, or
+ * "primary", one.  Also enforces that only items with the same DataKind/mime-type are added.
+ */
+public class KindSectionDataList extends ArrayList<KindSectionData> {
+
+    private static final String TAG = "CompactEditorView";
+
+    /**
+     * Returns the mime type for all DataKinds in this List.
+     */
+    public String getMimeType() {
+        if (isEmpty()) return null;
+        final String mimeType = get(0).getDataKind().mimeType;
+        // StructuredNames and Nicknames are a special case and go together under the
+        // StructuredName mime type
+        if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return StructuredName.CONTENT_ITEM_TYPE;
+        }
+        return mimeType;
+    }
+
+    /**
+     * Returns the DataKind for all entries in this List.
+     */
+    public DataKind getDataKind() {
+        return isEmpty() ? null : get(0).getDataKind();
+    }
+
+    /**
+     * Returns the "primary" KindSectionData and ValuesDelta that should be written for this List.
+     */
+    public Pair<KindSectionData,ValuesDelta> getEntryToWrite(AccountWithDataSet primaryAccount,
+            boolean hasNewContact) {
+        // Use the first writable contact that matches the primary account
+        if (primaryAccount != null && !hasNewContact) {
+            for (KindSectionData kindSectionData : this) {
+                if (kindSectionData.getAccountType().areContactsWritable()
+                        && !kindSectionData.getValuesDeltas().isEmpty()) {
+                    if (matchesAccount(primaryAccount, kindSectionData.getRawContactDelta())) {
+                        return new Pair<>(kindSectionData,
+                                kindSectionData.getValuesDeltas().get(0));
+                    }
+                }
+            }
+        }
+
+        // If no writable raw contact matched the primary account, or we're editing a read-only
+        // contact, just return the first writable entry.
+        for (KindSectionData kindSectionData : this) {
+            if (kindSectionData.getAccountType().areContactsWritable()) {
+                if (!kindSectionData.getValuesDeltas().isEmpty()) {
+                    return new Pair<>(kindSectionData, kindSectionData.getValuesDeltas().get(0));
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /** Whether the given RawContactDelta belong to the given account. */
+    private static 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());
+    }
+
+    /**
+     * Returns the "primary" KindSectionData and ValuesDelta that should be displayed to the user.
+     */
+    public Pair<KindSectionData,ValuesDelta> getEntryToDisplay(long id) {
+        final String mimeType = getMimeType();
+        if (mimeType == null) return null;
+
+        KindSectionData resultKindSectionData = null;
+        ValuesDelta resultValuesDelta = null;
+        if (id > 0) {
+            // Look for a match for the ID that was passed in
+            for (KindSectionData kindSectionData : this) {
+                resultValuesDelta = kindSectionData.getValuesDeltaById(id);
+                if (resultValuesDelta != null) {
+                    vlog(mimeType + ": matched kind section data by ID");
+                    resultKindSectionData = kindSectionData;
+                    break;
+                }
+            }
+        }
+        if (resultKindSectionData == null) {
+            // Look for a super primary entry
+            for (KindSectionData kindSectionData : this) {
+                resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
+                if (resultValuesDelta != null) {
+                    vlog(mimeType + ": matched super primary kind section data");
+                    resultKindSectionData = kindSectionData;
+                    break;
+                }
+            }
+        }
+        if (resultKindSectionData == null) {
+            // Fall back to the first non-empty value
+            for (KindSectionData kindSectionData : this) {
+                resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
+                if (resultValuesDelta != null) {
+                    vlog(mimeType + ": using first non empty value");
+                    resultKindSectionData = kindSectionData;
+                    break;
+                }
+            }
+        }
+        if (resultKindSectionData == null || resultValuesDelta == null) {
+            final List<ValuesDelta> valuesDeltaList = get(0).getValuesDeltas();
+            if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
+                vlog(mimeType + ": falling back to first empty entry");
+                resultValuesDelta = valuesDeltaList.get(0);
+                resultKindSectionData = get(0);
+            }
+        }
+        return resultKindSectionData != null && resultValuesDelta != null
+                ? new Pair<>(resultKindSectionData, resultValuesDelta) : null;
+    }
+
+    @Override
+    public boolean add(KindSectionData kindSectionData) {
+        if (kindSectionData == null) throw new NullPointerException();
+
+        // Enforce that only entries of the same type are added to this list
+        final String listMimeType = getMimeType();
+        if (listMimeType != null) {
+            final String newEntryMimeType = kindSectionData.getDataKind().mimeType;
+            if (isNameMimeType(listMimeType)) {
+                if (!isNameMimeType(newEntryMimeType)) {
+                    throw new IllegalArgumentException(
+                            "Can't add " + newEntryMimeType + " to list with type " + listMimeType);
+                }
+            } else if (!listMimeType.equals(newEntryMimeType)) {
+                throw new IllegalArgumentException(
+                        "Can't add " + newEntryMimeType + " to list with type " + listMimeType);
+            }
+        }
+        return super.add(kindSectionData);
+    }
+
+    // StructuredNames and Nicknames are a special case and go together
+    private static boolean isNameMimeType(String mimeType) {
+        return StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)
+                || Nickname.CONTENT_ITEM_TYPE.equals(mimeType);
+    }
+
+    private static void vlog(String message) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, message);
+        }
+    }
+}