Only iterate through raw contact delta list once (E3)

* Also, don't treat emails and phone numbers differently
  since we'll be adding a new KindSectionView for them.
* Introduced a multi account aware KindSectionData holder
  object to be used by the new KindSectionView
* Moved the utility method to get the icon for each kind
  from the exisitng KindSectionView to EditorUiUtils

Bug 23589603

Change-Id: I6b4858b093ce1b4a65c97b95febc61f2197db091
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 693b8fd..4aabf0e 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -149,6 +149,7 @@
     private boolean mIsUserProfile;
     private AccountWithDataSet mPrimaryAccount;
     private RawContactDelta mPrimaryRawContactDelta;
+    private Map<String,List<KindSectionData>> mKindSectionDataMap = new HashMap<>();
 
     // Account header
     private View mAccountHeaderContainer;
@@ -176,6 +177,7 @@
 
     private long mPhotoRawContactId;
 
+    private boolean mUsingDefaultNameEditorView;
     private StructuredNameEditorView mDefaultNameEditorView;
 
     public CompactRawContactsEditorView(Context context) {
@@ -320,6 +322,8 @@
             MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
             long photoId, long nameId, String readOnlyDisplayName, boolean hasNewContact,
             boolean isUserProfile, AccountWithDataSet primaryAccount) {
+        mKindSectionDataMap.clear();
+
         mNames.removeAllViews();
         mPhoneticNames.removeAllViews();
         mNicknames.removeAllViews();
@@ -327,6 +331,7 @@
         mEmails.removeAllViews();
         mOtherTypes.removeAllViews();
         mOtherTypesMap.clear();
+        mMoreFields.setVisibility(View.VISIBLE);
 
         if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
             return;
@@ -350,8 +355,8 @@
         vlog("state: setting compact editor state from " + rawContactDeltas);
         parseRawContactDeltas(rawContactDeltas);
         addAccountInfo();
-        addPhotoView(rawContactDeltas, viewIdGenerator, photoId, readOnlyDisplayName);
-        addStructuredNameView(rawContactDeltas, nameId, readOnlyDisplayName);
+        addPhotoView();
+        addStructuredNameView();
         addEditorViews(rawContactDeltas);
         updateKindEditorEmptyFields(mPhoneNumbers);
         updateKindEditorIcons(mPhoneNumbers);
@@ -406,140 +411,46 @@
                 }
             }
         }
-    }
 
-    private void addPhotoView(RawContactDeltaList rawContactDeltas,
-            ViewIdGenerator viewIdGenerator, long photoId, String readOnlyDisplayName) {
-        // If we're editing a read-only contact, the display name from the read-only
-        // contact is non empty and we can use it determine whether to back the photo editor with
-        // the empty new raw contact delta.  See go/editing-read-only-contacts
-        final boolean readOnlyContact = !TextUtils.isEmpty(readOnlyDisplayName);
-        if (readOnlyContact) {
-            for (RawContactDelta rawContactDelta : rawContactDeltas) {
-                if (!rawContactDelta.isVisible()) continue;
-                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+        RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+                mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+                StructuredName.CONTENT_ITEM_TYPE);
 
-                // Make sure we have a photo
-                RawContactModifier.ensureKindExists(
-                        rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(mPrimaryRawContactDelta,
+                mPrimaryRawContactDelta.getAccountType(mAccountTypeManager),
+                Photo.CONTENT_ITEM_TYPE);
 
-                final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-                if (accountType.areContactsWritable()) {
-                    for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
-                            Photo.CONTENT_ITEM_TYPE)) {
-                        if (valuesDelta != null) {
-                            // Break the loop but don't return because we need to keep going to
-                            // in order to show the photo from the read-only contact.
-                            mPhotoRawContactId = rawContactDelta.getRawContactId();
-                            mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                    /* readOnly =*/ false, mMaterialPalette, viewIdGenerator);
-                            break;
-                        }
-                    }
-                }
-            }
-        }
-
-        // Look for a match for the photo ID that was passed in
+        // Build the kind section data list map
         for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
+            if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
             final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            // Make sure we have a photo
-            RawContactModifier.ensureKindExists(
-                    rawContactDelta, accountType, Photo.CONTENT_ITEM_TYPE);
-
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                for (ValuesDelta valuesDelta
-                        : rawContactDelta.getMimeEntries(Photo.CONTENT_ITEM_TYPE)) {
-                    if (valuesDelta != null && valuesDelta.getId() != null
-                            && valuesDelta.getId().equals(photoId)) {
-                        if (readOnlyContact) {
-                            mPhoto.setPhoto(valuesDelta);
-                        } else {
-                            mPhotoRawContactId = rawContactDelta.getRawContactId();
-                            mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                    !accountType.areContactsWritable(),
-                                    mMaterialPalette, viewIdGenerator);
-                        }
-                        return;
-                    }
+            if (accountType == null) continue;
+            final List<DataKind> dataKinds = accountType.getSortedDataKinds();
+            final int dataKindSize = dataKinds == null ? 0 : dataKinds.size();
+            vlog("parse: " + dataKindSize + " dataKinds(s)");
+            for (int i = 0; i < dataKindSize; i++) {
+                final DataKind dataKind = dataKinds.get(i);
+                // Don't show read only values
+                if (dataKind == null || !dataKind.editable) continue;
+                // Get the kind section data list to add the new field to
+                List<KindSectionData> kindSectionDataList =
+                        mKindSectionDataMap.get(dataKind.mimeType);
+                if (kindSectionDataList == null) {
+                    kindSectionDataList = new ArrayList<>();
+                    mKindSectionDataMap.put(dataKind.mimeType, kindSectionDataList);
                 }
+                final KindSectionData kindSectionData =
+                        new KindSectionData(accountType, dataKind, rawContactDelta);
+                kindSectionDataList.add(kindSectionData);
+                vlog("parse: " + i + " " + dataKind.mimeType + " " +
+                        (kindSectionData.hasValuesDeltas()
+                                ? kindSectionData.getValuesDeltas().size() : null) + " value(s)");
             }
         }
-
-        // Look for a non-empty super primary photo
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                final ValuesDelta valuesDelta = getNonEmptySuperPrimaryValuesDeltas(
-                        rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
-                if (valuesDelta != null) {
-                    if (readOnlyContact) {
-                        mPhoto.setPhoto(valuesDelta);
-                    } else {
-                        mPhotoRawContactId = rawContactDelta.getRawContactId();
-                        mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                !accountType.areContactsWritable(), mMaterialPalette,
-                                viewIdGenerator);
-                    }
-                    return;
-                }
-            }
-        }
-        // We didn't find a non-empty super primary photo, use the first non-empty one
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                final List<ValuesDelta> valuesDeltas = getNonEmptyValuesDeltas(
-                        rawContactDelta, Photo.CONTENT_ITEM_TYPE, dataKind);
-                if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
-                    if (readOnlyContact) {
-                        mPhoto.setPhoto(valuesDeltas.get(0));
-                    } else {
-                        mPhotoRawContactId = rawContactDelta.getRawContactId();
-                        mPhoto.setValues(dataKind, valuesDeltas.get(0), rawContactDelta,
-                                !accountType.areContactsWritable(), mMaterialPalette,
-                                viewIdGenerator);
-                    }
-                    return;
-                }
-            }
-        }
-        // No suitable non-empty photo
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-            final DataKind dataKind = accountType.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
-            if (dataKind != null && dataKind.editable) {
-                final ValuesDelta valuesDelta = rawContactDelta.getSuperPrimaryEntry(
-                        dataKind.mimeType, /* forceSelection =*/ true);
-                if (valuesDelta != null) {
-                    if (readOnlyContact) {
-                        mPhoto.setPhoto(valuesDelta);
-                    } else {
-                        mPhotoRawContactId = rawContactDelta.getRawContactId();
-                        mPhoto.setValues(dataKind, valuesDelta, rawContactDelta,
-                                !accountType.areContactsWritable(), mMaterialPalette,
-                                viewIdGenerator);
-                    }
-                    return;
-                }
-            }
-        }
-        // Should not happen since we ensure the kind exists but if we unexpectedly get here
-        // we must remove the photo section so that it does not take up the entire view
-        mPhoto.setVisibility(View.GONE);
     }
 
     private void addAccountInfo() {
         if (mPrimaryRawContactDelta == null) {
-            vlog("account info: hidden because no raw contact delta");
             mAccountHeaderContainer.setVisibility(View.GONE);
             mAccountSelectorContainer.setVisibility(View.GONE);
             return;
@@ -626,134 +537,125 @@
         });
     }
 
-    private void addStructuredNameView(RawContactDeltaList rawContactDeltas, long nameId,
-            String readOnlyDisplayName) {
-        // If we're editing a read-only contact we want to display the name from the read-only
-        // contact in a structured name editor backed by the new raw contact that was created.
-        // The new raw contact is writable and merging it with the read-only contact allows us
-        // to edit the read-only contact. See go/editing-read-only-contacts
-        if (!TextUtils.isEmpty(readOnlyDisplayName)) {
-            for (RawContactDelta rawContactDelta : rawContactDeltas) {
-                if (!rawContactDelta.isVisible()) continue;
-                final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+    private void addPhotoView() {
+        // Get the kind section data and values delta that will back the photo view
+        final String mimeType = Photo.CONTENT_ITEM_TYPE;
+        Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mimeType, mPhotoId);
+        if (pair == null) {
+            wlog(mimeType + ": no kind section data parsed");
+            return;
+        }
+        final KindSectionData kindSectionData = pair.first;
+        final ValuesDelta valuesDelta = pair.second;
 
-                // Make sure we have a structured name
-                RawContactModifier.ensureKindExists(
-                        rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
+        // If 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.
+        // See go/editing-read-only-contacts
+        if (mReadOnlyDisplayName != null) {
+            mPhotoRawContactId = mPrimaryRawContactDelta.getRawContactId();
+        }
 
-                if (accountType.areContactsWritable()) {
-                    for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
-                            StructuredName.CONTENT_ITEM_TYPE)) {
-                        if (valuesDelta != null) {
-                            mNameValuesDelta = valuesDelta;
-                            final NameEditorListener nameEditorListener = new NameEditorListener(
-                                    mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                            final StructuredNameEditorView nameEditorView =
-                                    inflateStructuredNameEditorView(mNames, accountType,
-                                            mNameValuesDelta, rawContactDelta, nameEditorListener,
-                                            !accountType.areContactsWritable());
-                            nameEditorView.setDisplayName(readOnlyDisplayName);
-                            mNames.addView(nameEditorView);
-                            mDefaultNameEditorView = nameEditorView;
-                            return;
-                        }
-                    }
+        mPhotoRawContactId = kindSectionData.getRawContactDelta().getRawContactId();
+        mPhoto.setValues(kindSectionData.getDataKind(), valuesDelta,
+                kindSectionData.getRawContactDelta(),
+                !kindSectionData.getAccountType().areContactsWritable(), mMaterialPalette,
+                mViewIdGenerator);
+    }
+
+    private void addStructuredNameView() {
+        // Get the kind section data and values delta that will back the name view
+        final String mimeType = StructuredName.CONTENT_ITEM_TYPE;
+        Pair<KindSectionData,ValuesDelta> pair = getPrimaryKindSectionData(mimeType, mNameId);
+        if (pair == null) {
+            wlog(mimeType + ": no kind section data parsed");
+            return;
+        }
+        final KindSectionData kindSectionData = pair.first;
+        final ValuesDelta valuesDelta = pair.second;
+
+        // If we're editing a read-only contact we want to display the name from the
+        // read-only contact in the name editor backed by the new raw contact
+        // that was created. The new raw contact is the first writable one.
+        // See go/editing-read-only-contacts
+        // TODO
+        if (!TextUtils.isEmpty(mReadOnlyDisplayName)) {
+            for (KindSectionData data : mKindSectionDataMap.get(mimeType)) {
+                if (data.getAccountType().areContactsWritable()) {
+                    vlog(mimeType + ": using name from read-only contact");
+                    mUsingDefaultNameEditorView = true;
+                    break;
                 }
             }
         }
 
-        // Look for a match for the name ID that was passed in
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+        final NameEditorListener nameEditorListener = new NameEditorListener(valuesDelta,
+                kindSectionData.getRawContactDelta().getRawContactId(), mListener);
+        final StructuredNameEditorView nameEditorView = inflateStructuredNameEditorView(
+                mNames, kindSectionData.getAccountType(), valuesDelta,
+                kindSectionData.getRawContactDelta(), nameEditorListener,
+                !kindSectionData.getAccountType().areContactsWritable());
+        mNames.addView(nameEditorView);
 
-            // Make sure we have a structured name
-            RawContactModifier.ensureKindExists(
-                    rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
+        // TODO: Remove this after eliminating the full editor
+        mNameValuesDelta = valuesDelta;
+        if (mUsingDefaultNameEditorView) {
+            mDefaultNameEditorView = nameEditorView;
+        }
+    }
 
-            // Note use of pseudo mime type to get the DataKind and StructuredName to get value
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
+    private Pair<KindSectionData,ValuesDelta> getPrimaryKindSectionData(String mimeType, long id) {
+        final List<KindSectionData> kindSectionDataList = mKindSectionDataMap.get(mimeType);
+        if (kindSectionDataList == null || kindSectionDataList.isEmpty()) {
+            wlog(mimeType + ": no kind section data parsed");
+            return null;
+        }
 
-            for (ValuesDelta valuesDelta : rawContactDelta.getMimeEntries(
-                    StructuredName.CONTENT_ITEM_TYPE)) {
-                if (valuesDelta != null && valuesDelta.getId() != null
-                        && valuesDelta.getId().equals(nameId)) {
-                    mNameValuesDelta = valuesDelta;
-                    final NameEditorListener nameEditorListener = new NameEditorListener(
-                            mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                    mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                            mNameValuesDelta, rawContactDelta, nameEditorListener,
-                            !accountType.areContactsWritable()));
-                    return;
+        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(mimeType + ": matched kind section data by ID");
+                    resultKindSectionData = kindSectionData;
+                    break;
                 }
             }
         }
-        // Look for a super primary name
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            final ValuesDelta superPrimaryValuesDelta = getNonEmptySuperPrimaryValuesDeltas(
-                    rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
-            if (superPrimaryValuesDelta != null) {
-                // Our first preference is for a non-empty super primary name
-                final NameEditorListener nameEditorListener = new NameEditorListener(
-                        superPrimaryValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                        superPrimaryValuesDelta, rawContactDelta, nameEditorListener,
-                        !accountType.areContactsWritable()));
-                return;
+        if (resultKindSectionData == null) {
+            // Look for a super primary photo
+            for (KindSectionData kindSectionData : kindSectionDataList) {
+                resultValuesDelta = kindSectionData.getSuperPrimaryValuesDelta();
+                if (resultValuesDelta != null) {
+                    wlog(mimeType + ": matched super primary kind section data");
+                    resultKindSectionData = kindSectionData;
+                    break;
+                }
             }
         }
-        // We didn't find a super primary name
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            final List<ValuesDelta> nonEmptyValuesDeltas = getNonEmptyValuesDeltas(
-                    rawContactDelta, StructuredName.CONTENT_ITEM_TYPE, dataKind);
-            if (nonEmptyValuesDeltas != null && !nonEmptyValuesDeltas.isEmpty()) {
-                // Take the first non-empty name
-                mNameValuesDelta = nonEmptyValuesDeltas.get(0);
-                final NameEditorListener nameEditorListener = new NameEditorListener(
-                        mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                        mNameValuesDelta, rawContactDelta, nameEditorListener,
-                        !accountType.areContactsWritable()));
-                return;
+        if (resultKindSectionData == null) {
+            // Fall back to the first non-empty value
+            for (KindSectionData kindSectionData : kindSectionDataList) {
+                resultValuesDelta = kindSectionData.getFirstNonEmptyValuesDelta();
+                if (resultValuesDelta != null) {
+                    vlog(mimeType + ": using first non empty value");
+                    resultKindSectionData = kindSectionData;
+                    break;
+                }
             }
         }
-        for (RawContactDelta rawContactDelta : rawContactDeltas) {
-            if (!rawContactDelta.isVisible()) continue;
-            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
-
-            final DataKind dataKind = accountType.getKindForMimetype(
-                    DataKind.PSEUDO_MIME_TYPE_DISPLAY_NAME);
-            if (dataKind == null || !dataKind.editable) continue;
-
-            // Fall back to the first entry
-            final ArrayList<ValuesDelta> valuesDeltas = rawContactDelta.getMimeEntries(
-                    StructuredName.CONTENT_ITEM_TYPE);
-            if (valuesDeltas != null && !valuesDeltas.isEmpty()) {
-                mNameValuesDelta = valuesDeltas.get(0);
-                final NameEditorListener nameEditorListener = new NameEditorListener(
-                        mNameValuesDelta, rawContactDelta.getRawContactId(), mListener);
-                mNames.addView(inflateStructuredNameEditorView(mNames, accountType,
-                        mNameValuesDelta, rawContactDelta, nameEditorListener,
-                        !accountType.areContactsWritable()));
-                return;
+        if (resultKindSectionData == null || resultValuesDelta == null) {
+            final List<ValuesDelta> valuesDeltaList = kindSectionDataList.get(0).getValuesDeltas();
+            if (valuesDeltaList != null && !valuesDeltaList.isEmpty()) {
+                vlog(mimeType + ": 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 void addEditorViews(RawContactDeltaList rawContactDeltas) {
@@ -1011,4 +913,14 @@
             Log.v(TAG, message);
         }
     }
+
+    private static void wlog(String message) {
+        if (Log.isLoggable(TAG, Log.WARN)) {
+            Log.w(TAG, message);
+        }
+    }
+
+    private static void elog(String message) {
+        Log.e(TAG, message);
+    }
 }