Rename editor files

Drop the Compact prefix since we only have one editor now.
Remove KindSectionDataList since it's now unused.

Test:
Tested build

Bug: 31088704
Change-Id: Ia5ac295804a14f79d0c837b151e33aabc60aa3d0
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
new file mode 100644
index 0000000..bbd23d4
--- /dev/null
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -0,0 +1,1039 @@
+/*
+ * 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 android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListPopupWindow;
+import android.widget.TextView;
+
+import com.android.contacts.R;
+import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
+import com.android.contacts.common.model.AccountTypeManager;
+import com.android.contacts.common.model.RawContactDelta;
+import com.android.contacts.common.model.RawContactDeltaList;
+import com.android.contacts.common.model.RawContactModifier;
+import com.android.contacts.common.model.ValuesDelta;
+import com.android.contacts.common.model.account.AccountDisplayInfo;
+import com.android.contacts.common.model.account.AccountDisplayInfoFactory;
+import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.common.model.account.AccountWithDataSet;
+import com.android.contacts.common.model.account.DeviceLocalAccountType;
+import com.android.contacts.common.model.account.SimAccountType;
+import com.android.contacts.common.model.dataitem.CustomDataItem;
+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.UiClosables;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * View to display information from multiple {@link RawContactDelta}s grouped together.
+ */
+public class RawContactEditorView extends LinearLayout implements View.OnClickListener {
+
+    static final String TAG = "RawContactEditorView";
+
+    /**
+     * Callbacks for hosts of {@link RawContactEditorView}s.
+     */
+    public interface Listener {
+
+        /**
+         * Invoked when the structured name editor field has changed.
+         *
+         * @param rawContactId The raw contact ID from the underlying {@link RawContactDelta}.
+         * @param valuesDelta The values from the underlying {@link RawContactDelta}.
+         */
+        public void onNameFieldChanged(long rawContactId, ValuesDelta valuesDelta);
+
+        /**
+         * Invoked when the editor should rebind editors for a new account.
+         *
+         * @param oldState Old data being edited.
+         * @param oldAccount Old account associated with oldState.
+         * @param newAccount New account to be used.
+         */
+        public void onRebindEditorsForNewContact(RawContactDelta oldState,
+                AccountWithDataSet oldAccount, AccountWithDataSet newAccount);
+
+        /**
+         * Invoked when no editors could be bound for the contact.
+         */
+        public void onBindEditorsFailed();
+
+        /**
+         * Invoked after editors have been bound for the contact.
+         */
+        public void onEditorsBound();
+
+        /**
+         * Invoked when a rawcontact from linked contacts is selected in editor.
+         */
+        void onRawContactSelected(long rawContactId, boolean isReadOnly);
+    }
+
+    /**
+     * Used to list the account info for the given raw contacts list.
+     */
+    private static final class RawContactAccountListAdapter extends BaseAdapter {
+        private final LayoutInflater mInflater;
+        private final Context mContext;
+        private final RawContactDeltaList mRawContactDeltas;
+
+        public RawContactAccountListAdapter(Context context, RawContactDeltaList rawContactDeltas) {
+            mContext = context;
+            mRawContactDeltas = new RawContactDeltaList();
+            for (RawContactDelta rawContactDelta : rawContactDeltas) {
+                if (rawContactDelta.isVisible() && rawContactDelta.getRawContactId() > 0) {
+                    mRawContactDeltas.add(rawContactDelta);
+                }
+            }
+            mInflater = LayoutInflater.from(context);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View resultView = convertView != null ? convertView
+                    : mInflater.inflate(R.layout.account_selector_list_item, parent, false);
+
+            final RawContactDelta rawContactDelta = mRawContactDeltas.get(position);
+
+            final TextView text1 = (TextView) resultView.findViewById(android.R.id.text1);
+            final AccountType accountType = rawContactDelta.getRawContactAccountType(mContext);
+            text1.setText(accountType.getDisplayLabel(mContext));
+
+            final TextView text2 = (TextView) resultView.findViewById(android.R.id.text2);
+            final String accountName = rawContactDelta.getAccountName();
+            if (TextUtils.isEmpty(accountName) || accountType instanceof DeviceLocalAccountType
+                    || accountType instanceof SimAccountType) {
+                text2.setVisibility(View.GONE);
+            } else {
+                // Truncate email addresses in the middle so we don't lose the domain
+                text2.setText(accountName);
+                text2.setEllipsize(TextUtils.TruncateAt.MIDDLE);
+            }
+
+            final ImageView icon = (ImageView) resultView.findViewById(android.R.id.icon);
+            icon.setImageDrawable(accountType.getDisplayIcon(mContext));
+
+            return resultView;
+        }
+
+        @Override
+        public int getCount() {
+            return mRawContactDeltas.size();
+        }
+
+        @Override
+        public RawContactDelta getItem(int position) {
+            return mRawContactDeltas.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return getItem(position).getRawContactId();
+        }
+    }
+
+    /**
+     * 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>
+     *     <li>Group membership is placed at the end</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,
+                Nickname.CONTENT_ITEM_TYPE,
+                Organization.CONTENT_ITEM_TYPE,
+                Phone.CONTENT_ITEM_TYPE,
+                SipAddress.CONTENT_ITEM_TYPE,
+                Email.CONTENT_ITEM_TYPE,
+                StructuredPostal.CONTENT_ITEM_TYPE,
+                Im.CONTENT_ITEM_TYPE,
+                Website.CONTENT_ITEM_TYPE,
+                Event.CONTENT_ITEM_TYPE,
+                Relation.CONTENT_ITEM_TYPE,
+                Note.CONTENT_ITEM_TYPE,
+                GroupMembership.CONTENT_ITEM_TYPE
+        });
+
+        @Override
+        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);
+
+            // Fallback to alphabetical ordering of the mime type if both are not found
+            if (index1 < 0 && index2 < 0) return mimeType1.compareTo(mimeType2);
+            if (index1 < 0) return 1;
+            if (index2 < 0) return -1;
+
+            return index1 < index2 ? -1 : 1;
+        }
+    }
+
+    public static class SavedState extends BaseSavedState {
+
+        public static final Parcelable.Creator<SavedState> CREATOR =
+                new Parcelable.Creator<SavedState>() {
+                    public SavedState createFromParcel(Parcel in) {
+                        return new SavedState(in);
+                    }
+                    public SavedState[] newArray(int size) {
+                        return new SavedState[size];
+                    }
+                };
+
+        private boolean mIsExpanded;
+
+        public SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            mIsExpanded = in.readInt() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(mIsExpanded ? 1 : 0);
+        }
+    }
+
+    private RawContactEditorView.Listener mListener;
+
+    private AccountTypeManager mAccountTypeManager;
+    private AccountDisplayInfoFactory mAccountDisplayInfoFactory;
+    private LayoutInflater mLayoutInflater;
+
+    private ViewIdGenerator mViewIdGenerator;
+    private MaterialColorMapUtils.MaterialPalette mMaterialPalette;
+    private boolean mHasNewContact;
+    private boolean mIsUserProfile;
+    private AccountWithDataSet mPrimaryAccount;
+    private RawContactDeltaList mRawContactDeltas;
+    private RawContactDelta mCurrentRawContactDelta;
+    private long mRawContactIdToDisplayAlone = -1;
+    private boolean mIsEditingReadOnlyRawContactWithNewContact;
+    private Map<String, KindSectionData> mKindSectionDataMap = new HashMap<>();
+    private Set<String> mSortedMimetypes = new TreeSet<>(new MimeTypeComparator());
+
+    // Account header
+    private View mAccountHeaderContainer;
+    private TextView mAccountHeaderType;
+    private TextView mAccountHeaderName;
+    private ImageView mAccountHeaderIcon;
+    private ImageView mAccountHeaderExpanderIcon;
+
+    private PhotoEditorView mPhotoView;
+    private ViewGroup mKindSectionViews;
+    private Map<String, KindSectionView> mKindSectionViewMap = new HashMap<>();
+    private View mMoreFields;
+
+    private boolean mIsExpanded;
+
+    private ValuesDelta mPhotoValuesDelta;
+
+    public RawContactEditorView(Context context) {
+        super(context);
+    }
+
+    public RawContactEditorView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Sets the receiver for {@link RawContactEditorView} callbacks.
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mAccountTypeManager = AccountTypeManager.getInstance(getContext());
+        mAccountDisplayInfoFactory = AccountDisplayInfoFactory.forWritableAccounts(getContext());
+        mLayoutInflater = (LayoutInflater)
+                getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        // Account header
+        mAccountHeaderContainer = findViewById(R.id.account_header_container);
+        mAccountHeaderType = (TextView) findViewById(R.id.account_type);
+        mAccountHeaderName = (TextView) findViewById(R.id.account_name);
+        mAccountHeaderIcon = (ImageView) findViewById(R.id.account_type_icon);
+        mAccountHeaderExpanderIcon = (ImageView) findViewById(R.id.account_expander_icon);
+
+        mPhotoView = (PhotoEditorView) findViewById(R.id.photo_editor);
+        mKindSectionViews = (LinearLayout) findViewById(R.id.kind_section_views);
+        mMoreFields = findViewById(R.id.more_fields);
+        mMoreFields.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view.getId() == R.id.more_fields) {
+            showAllFields();
+        }
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        super.setEnabled(enabled);
+        final int childCount = mKindSectionViews.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            mKindSectionViews.getChildAt(i).setEnabled(enabled);
+        }
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        final Parcelable superState = super.onSaveInstanceState();
+        final SavedState savedState = new SavedState(superState);
+        savedState.mIsExpanded = mIsExpanded;
+        return savedState;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if(!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+        final SavedState savedState = (SavedState) state;
+        super.onRestoreInstanceState(savedState.getSuperState());
+        mIsExpanded = savedState.mIsExpanded;
+        if (mIsExpanded) {
+            showAllFields();
+        }
+    }
+
+    /**
+     * Pass through to {@link PhotoEditorView#setListener}.
+     */
+    public void setPhotoListener(PhotoEditorView.Listener listener) {
+        mPhotoView.setListener(listener);
+    }
+
+    public void removePhoto() {
+        mPhotoValuesDelta.setFromTemplate(true);
+        mPhotoValuesDelta.put(Photo.PHOTO, (byte[]) null);
+        mPhotoValuesDelta.put(Photo.PHOTO_FILE_ID, (String) null);
+
+        mPhotoView.removePhoto();
+    }
+
+    /**
+     * Pass through to {@link PhotoEditorView#setFullSizedPhoto(Uri)}.
+     */
+    public void setFullSizePhoto(Uri photoUri) {
+        mPhotoView.setFullSizedPhoto(photoUri);
+    }
+
+    public void updatePhoto(Uri photoUri) {
+        mPhotoValuesDelta.setFromTemplate(false);
+        // Unset primary for all photos
+        unsetSuperPrimaryFromAllPhotos();
+        // Mark the currently displayed photo as primary
+        mPhotoValuesDelta.setSuperPrimary(true);
+
+        // Even though high-res photos cannot be saved by passing them via
+        // an EntityDeltaList (since they cause the Bundle size limit to be
+        // exceeded), we still pass a low-res thumbnail. This simplifies
+        // code all over the place, because we don't have to test whether
+        // there is a change in EITHER the delta-list OR a changed photo...
+        // this way, there is always a change in the delta-list.
+        try {
+            final byte[] bytes = EditorUiUtils.getCompressedThumbnailBitmapBytes(
+                    getContext(), photoUri);
+            if (bytes != null) {
+                mPhotoValuesDelta.setPhoto(bytes);
+            }
+        } catch (FileNotFoundException e) {
+            elog("Failed to get bitmap from photo Uri");
+        }
+
+        mPhotoView.setFullSizedPhoto(photoUri);
+    }
+
+    private void unsetSuperPrimaryFromAllPhotos() {
+        for (int i = 0; i < mRawContactDeltas.size(); i++) {
+            final RawContactDelta rawContactDelta = mRawContactDeltas.get(i);
+            if (!rawContactDelta.hasMimeEntries(Photo.CONTENT_ITEM_TYPE)) {
+                continue;
+            }
+            final List<ValuesDelta> photosDeltas =
+                    mRawContactDeltas.get(i).getMimeEntries(Photo.CONTENT_ITEM_TYPE);
+            if (photosDeltas == null) {
+                continue;
+            }
+            for (int j = 0; j < photosDeltas.size(); j++) {
+                photosDeltas.get(j).setSuperPrimary(false);
+            }
+        }
+    }
+
+    /**
+     * Pass through to {@link PhotoEditorView#isWritablePhotoSet}.
+     */
+    public boolean isWritablePhotoSet() {
+        return mPhotoView.isWritablePhotoSet();
+    }
+
+    /**
+     * Get the raw contact ID for the current photo.
+     */
+    public long getPhotoRawContactId() {
+        return mCurrentRawContactDelta.getRawContactId();
+    }
+
+    public StructuredNameEditorView getNameEditorView() {
+        final KindSectionView nameKindSectionView = mKindSectionViewMap
+                .get(StructuredName.CONTENT_ITEM_TYPE);
+        return nameKindSectionView == null
+                ? null : nameKindSectionView.getNameEditorView();
+    }
+
+    public RawContactDelta getCurrentRawContactDelta() {
+        return mCurrentRawContactDelta;
+    }
+
+    /**
+     * Marks the raw contact photo given as primary for the aggregate contact.
+     */
+    public void setPrimaryPhoto() {
+
+        // Update values delta
+        final ValuesDelta valuesDelta = mCurrentRawContactDelta
+                .getSuperPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
+        if (valuesDelta == null) {
+            Log.wtf(TAG, "setPrimaryPhoto: had no ValuesDelta for the current RawContactDelta");
+            return;
+        }
+        valuesDelta.setFromTemplate(false);
+        unsetSuperPrimaryFromAllPhotos();
+        valuesDelta.setSuperPrimary(true);
+    }
+
+    public View getAggregationAnchorView() {
+        final StructuredNameEditorView nameEditorView = getNameEditorView();
+        return nameEditorView != null ? nameEditorView.findViewById(R.id.anchor_view) : null;
+    }
+
+    public void setGroupMetaData(Cursor groupMetaData) {
+        final KindSectionView groupKindSectionView =
+                mKindSectionViewMap.get(GroupMembership.CONTENT_ITEM_TYPE);
+        if (groupKindSectionView == null) {
+            return;
+        }
+        groupKindSectionView.setGroupMetaData(groupMetaData);
+        if (mIsExpanded) {
+            groupKindSectionView.setHideWhenEmpty(false);
+            groupKindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
+        }
+    }
+
+    public void setState(RawContactDeltaList rawContactDeltas,
+            MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
+            boolean hasNewContact, boolean isUserProfile, AccountWithDataSet primaryAccount,
+            long rawContactIdToDisplayAlone, boolean isEditingReadOnlyRawContactWithNewContact) {
+
+        mRawContactDeltas = rawContactDeltas;
+        mRawContactIdToDisplayAlone = rawContactIdToDisplayAlone;
+        mIsEditingReadOnlyRawContactWithNewContact = isEditingReadOnlyRawContactWithNewContact;
+
+        mKindSectionViewMap.clear();
+        mKindSectionViews.removeAllViews();
+        mMoreFields.setVisibility(View.VISIBLE);
+
+        mMaterialPalette = materialPalette;
+        mViewIdGenerator = viewIdGenerator;
+
+        mHasNewContact = hasNewContact;
+        mIsUserProfile = isUserProfile;
+        mPrimaryAccount = primaryAccount;
+        if (mPrimaryAccount == null) {
+            mPrimaryAccount = ContactEditorUtils.create(getContext()).getOnlyOrDefaultAccount();
+        }
+        vlog("state: primary " + mPrimaryAccount);
+
+        // Parse the given raw contact deltas
+        if (rawContactDeltas == null || rawContactDeltas.isEmpty()) {
+            elog("No raw contact deltas");
+            if (mListener != null) mListener.onBindEditorsFailed();
+            return;
+        }
+        pickRawContactDelta();
+        parseRawContactDelta();
+        if (mKindSectionDataMap.isEmpty()) {
+            elog("No kind section data parsed from RawContactDelta(s)");
+            if (mListener != null) mListener.onBindEditorsFailed();
+            return;
+        }
+
+        final KindSectionData nameSectionData =
+                mKindSectionDataMap.get(StructuredName.CONTENT_ITEM_TYPE);
+        // Ensure that a structured name and photo exists
+        if (nameSectionData != null) {
+            final RawContactDelta rawContactDelta =
+                    nameSectionData.getRawContactDelta();
+            RawContactModifier.ensureKindExists(
+                    rawContactDelta,
+                    rawContactDelta.getAccountType(mAccountTypeManager),
+                    StructuredName.CONTENT_ITEM_TYPE);
+            RawContactModifier.ensureKindExists(
+                    rawContactDelta,
+                    rawContactDelta.getAccountType(mAccountTypeManager),
+                    Photo.CONTENT_ITEM_TYPE);
+        }
+
+        // Setup the view
+        addPhotoView();
+        if (isReadOnlyRawContact()) {
+            // We're want to display the inputs fields for a single read only raw contact
+            addReadOnlyRawContactEditorViews();
+        } else {
+            setupEditorNormally();
+        }
+        if (mListener != null) mListener.onEditorsBound();
+    }
+
+    private void setupEditorNormally() {
+        addAccountInfo();
+        addKindSectionViews();
+
+        mMoreFields.setVisibility(hasMoreFields() ? View.VISIBLE : View.GONE);
+
+        if (mIsExpanded) showAllFields();
+    }
+
+    private boolean isReadOnlyRawContact() {
+        return !mCurrentRawContactDelta.getAccountType(mAccountTypeManager).areContactsWritable();
+    }
+
+    private void pickRawContactDelta() {
+        // Build the kind section data list map
+        vlog("parse: " + mRawContactDeltas.size() + " rawContactDelta(s)");
+        for (int j = 0; j < mRawContactDeltas.size(); j++) {
+            final RawContactDelta rawContactDelta = mRawContactDeltas.get(j);
+            vlog("parse: " + j + " rawContactDelta" + rawContactDelta);
+            if (rawContactDelta == null || !rawContactDelta.isVisible()) continue;
+            final AccountType accountType = rawContactDelta.getAccountType(mAccountTypeManager);
+            if (accountType == null) continue;
+
+            if (mRawContactIdToDisplayAlone > 0) {
+                // Look for the raw contact if specified.
+                if (rawContactDelta.getRawContactId().equals(mRawContactIdToDisplayAlone)) {
+                    mCurrentRawContactDelta = rawContactDelta;
+                    return;
+                }
+            } else if (mPrimaryAccount != null
+                    && mPrimaryAccount.equals(rawContactDelta.getAccountWithDataSet())) {
+                // Otherwise try to find the one that matches the default.
+                mCurrentRawContactDelta = rawContactDelta;
+                return;
+            } else if (accountType.areContactsWritable()){
+                // TODO: Find better raw contact delta
+                // Just select an arbitrary writable contact.
+                mCurrentRawContactDelta = rawContactDelta;
+            }
+        }
+
+    }
+
+    private void parseRawContactDelta() {
+        mKindSectionDataMap.clear();
+        mSortedMimetypes.clear();
+
+        final AccountType accountType = mCurrentRawContactDelta.getAccountType(mAccountTypeManager);
+        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);
+            if (dataKind == null) {
+                vlog("parse: " + i + " " + dataKind.mimeType + " dropped null data kind");
+                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;
+            }
+
+            // Skip custom fields
+            // TODO: Handle them when we implement editing custom fields.
+            if (CustomDataItem.MIMETYPE_CUSTOM_FIELD.equals(mimeType)) {
+                vlog("parse: " + i + " " + dataKind.mimeType + " dropped custom field");
+                continue;
+            }
+
+            final KindSectionData kindSectionData =
+                    new KindSectionData(accountType, dataKind, mCurrentRawContactDelta);
+            mKindSectionDataMap.put(mimeType, kindSectionData);
+            mSortedMimetypes.add(mimeType);
+
+            vlog("parse: " + i + " " + dataKind.mimeType + " " +
+                    kindSectionData.getValuesDeltas().size() + " value(s) " +
+                    kindSectionData.getNonEmptyValuesDeltas().size() + " non-empty value(s) " +
+                    kindSectionData.getVisibleValuesDeltas().size() +
+                    " visible value(s)");
+        }
+    }
+
+    private void addReadOnlyRawContactEditorViews() {
+        mKindSectionViews.removeAllViews();
+        addAccountInfo();
+        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
+                getContext());
+        final AccountType type = mCurrentRawContactDelta.getAccountType(accountTypes);
+
+        // Bail if invalid state or source
+        if (type == null) return;
+
+        // Make sure we have StructuredName
+        RawContactModifier.ensureKindExists(
+                mCurrentRawContactDelta, type, StructuredName.CONTENT_ITEM_TYPE);
+
+        ValuesDelta primary;
+
+        // Name
+        final Context context = getContext();
+        final Resources res = context.getResources();
+        primary = mCurrentRawContactDelta.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
+        final String name = primary != null ? primary.getAsString(StructuredName.DISPLAY_NAME) :
+            getContext().getString(R.string.missing_name);
+        final Drawable nameDrawable = context.getDrawable(R.drawable.ic_person_24dp);
+        final String nameContentDescription = res.getString(R.string.header_name_entry);
+        bindData(nameDrawable, nameContentDescription, name, /* type */ null,
+                /* isFirstEntry */ true);
+
+        // Phones
+        final ArrayList<ValuesDelta> phones = mCurrentRawContactDelta
+                .getMimeEntries(Phone.CONTENT_ITEM_TYPE);
+        final Drawable phoneDrawable = context.getDrawable(R.drawable.ic_phone_24dp);
+        final String phoneContentDescription = res.getString(R.string.header_phone_entry);
+        if (phones != null) {
+            boolean isFirstPhoneBound = true;
+            for (ValuesDelta phone : phones) {
+                final String phoneNumber = phone.getPhoneNumber();
+                if (TextUtils.isEmpty(phoneNumber)) {
+                    continue;
+                }
+                final String formattedNumber = PhoneNumberUtilsCompat.formatNumber(
+                        phoneNumber, phone.getPhoneNormalizedNumber(),
+                        GeoUtil.getCurrentCountryIso(getContext()));
+                CharSequence phoneType = null;
+                if (phone.hasPhoneType()) {
+                    phoneType = Phone.getTypeLabel(
+                            res, phone.getPhoneType(), phone.getPhoneLabel());
+                }
+                bindData(phoneDrawable, phoneContentDescription, formattedNumber, phoneType,
+                        isFirstPhoneBound, true);
+                isFirstPhoneBound = false;
+            }
+        }
+
+        // Emails
+        final ArrayList<ValuesDelta> emails = mCurrentRawContactDelta
+                .getMimeEntries(Email.CONTENT_ITEM_TYPE);
+        final Drawable emailDrawable = context.getDrawable(R.drawable.ic_email_24dp);
+        final String emailContentDescription = res.getString(R.string.header_email_entry);
+        if (emails != null) {
+            boolean isFirstEmailBound = true;
+            for (ValuesDelta email : emails) {
+                final String emailAddress = email.getEmailData();
+                if (TextUtils.isEmpty(emailAddress)) {
+                    continue;
+                }
+                CharSequence emailType = null;
+                if (email.hasEmailType()) {
+                    emailType = Email.getTypeLabel(
+                            res, email.getEmailType(), email.getEmailLabel());
+                }
+                bindData(emailDrawable, emailContentDescription, emailAddress, emailType,
+                        isFirstEmailBound);
+                isFirstEmailBound = false;
+            }
+        }
+
+        mKindSectionViews.setVisibility(mKindSectionViews.getChildCount() > 0 ? VISIBLE : GONE);
+        // Hide the "More fields" link
+        mMoreFields.setVisibility(GONE);
+    }
+
+    private void bindData(Drawable icon, String iconContentDescription, CharSequence data,
+            CharSequence type, boolean isFirstEntry) {
+        bindData(icon, iconContentDescription, data, type, isFirstEntry, false);
+    }
+
+    private void bindData(Drawable icon, String iconContentDescription, CharSequence data,
+            CharSequence type, boolean isFirstEntry, boolean forceLTR) {
+        final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        final View field = inflater.inflate(R.layout.item_read_only_field, mKindSectionViews,
+                false);
+        if (isFirstEntry) {
+            final ImageView imageView = (ImageView) field.findViewById(R.id.kind_icon);
+            imageView.setImageDrawable(icon);
+            imageView.setContentDescription(iconContentDescription);
+        } else {
+            final ImageView imageView = (ImageView) field.findViewById(R.id.kind_icon);
+            imageView.setVisibility(View.INVISIBLE);
+            imageView.setContentDescription(null);
+        }
+        final TextView dataView = (TextView) field.findViewById(R.id.data);
+        dataView.setText(data);
+        if (forceLTR) {
+            dataView.setTextDirection(View.TEXT_DIRECTION_LTR);
+        }
+        final TextView typeView = (TextView) field.findViewById(R.id.type);
+        if (!TextUtils.isEmpty(type)) {
+            typeView.setText(type);
+        } else {
+            typeView.setVisibility(View.GONE);
+        }
+        mKindSectionViews.addView(field);
+    }
+
+    private void addAccountInfo() {
+        mAccountHeaderContainer.setVisibility(View.GONE);
+
+        final AccountDisplayInfo account =
+                mAccountDisplayInfoFactory.getAccountDisplayInfoFor(mCurrentRawContactDelta);
+
+        // Get the account information for the primary raw contact delta
+        final String accountLabel = mIsUserProfile
+                ? EditorUiUtils.getAccountHeaderLabelForMyProfile(getContext(), account)
+                : account.getNameLabel().toString();
+
+        // Either the account header or selector should be shown, not both.
+        final List<AccountWithDataSet> accounts =
+                AccountTypeManager.getInstance(getContext()).getAccounts(true);
+
+        if (mHasNewContact && !mIsUserProfile) {
+            if (accounts.size() > 1) {
+                addAccountSelector(mCurrentRawContactDelta, accountLabel);
+            } else {
+                addAccountHeader(accountLabel);
+            }
+        } else {
+            // The raw contact selector should only display linked raw contacts that can be edited
+            // in the full editor (i.e. they are not newly created raw contacts)
+            final RawContactAccountListAdapter adapter =  new RawContactAccountListAdapter(
+                    getContext(), getRawContactDeltaListForSelector(mRawContactDeltas));
+            if (adapter.getCount() > 0 && !mIsEditingReadOnlyRawContactWithNewContact) {
+                addRawContactAccountSelector(accountLabel, adapter);
+            } else {
+                addAccountHeader(accountLabel);
+            }
+        }
+    }
+
+    private RawContactDeltaList getRawContactDeltaListForSelector(
+            RawContactDeltaList rawContactDeltas) {
+        // Sort raw contacts so google accounts come first
+        Collections.sort(rawContactDeltas, new RawContactDeltaComparator(getContext()));
+
+        final RawContactDeltaList result = new RawContactDeltaList();
+        for (int i = 0; i < rawContactDeltas.size(); i++) {
+            final RawContactDelta rawContactDelta = rawContactDeltas.get(i);
+            if (rawContactDelta.isVisible() && rawContactDelta.getRawContactId() > 0) {
+                // Only add raw contacts that can be opened in the editor
+                result.add(rawContactDelta);
+            }
+        }
+        // Don't return a list of size 1 that would just open the current raw contact being edited.
+        if (result.size() == 1 && result.get(0).getRawContactAccountType(
+                getContext()).areContactsWritable()) {
+            result.clear();
+            return result;
+        }
+        return result;
+    }
+
+    private void addAccountHeader(String accountLabel) {
+        mAccountHeaderContainer.setVisibility(View.VISIBLE);
+
+        // Set the account name
+        mAccountHeaderName.setVisibility(View.VISIBLE);
+        mAccountHeaderName.setText(accountLabel);
+
+        // Set the account type
+        final String selectorTitle = getResources().getString(isReadOnlyRawContact() ?
+                R.string.editor_account_selector_read_only_title :
+                R.string.editor_account_selector_title);
+        mAccountHeaderType.setText(selectorTitle);
+
+        // Set the icon
+        final AccountType accountType =
+                mCurrentRawContactDelta.getRawContactAccountType(getContext());
+        mAccountHeaderIcon.setImageDrawable(accountType.getDisplayIcon(getContext()));
+
+        // Set the content description
+        mAccountHeaderContainer.setContentDescription(
+                EditorUiUtils.getAccountInfoContentDescription(accountLabel,
+                        selectorTitle));
+    }
+
+    private void addAccountSelector(final RawContactDelta rawContactDelta, CharSequence nameLabel) {
+        final View.OnClickListener onClickListener = new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
+                final AccountsListAdapter adapter =
+                        new AccountsListAdapter(getContext(),
+                                AccountsListAdapter.AccountListFilter.ACCOUNTS_CONTACT_WRITABLE,
+                                mPrimaryAccount);
+                popup.setWidth(mAccountHeaderContainer.getWidth());
+                popup.setAnchorView(mAccountHeaderContainer);
+                popup.setAdapter(adapter);
+                popup.setModal(true);
+                popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+                popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                    @Override
+                    public void onItemClick(AdapterView<?> parent, View view, int position,
+                            long id) {
+                        UiClosables.closeQuietly(popup);
+                        final AccountWithDataSet newAccount = adapter.getItem(position);
+                        if (mListener != null && !mPrimaryAccount.equals(newAccount)) {
+                            mIsExpanded = false;
+                            mListener.onRebindEditorsForNewContact(
+                                    rawContactDelta,
+                                    mPrimaryAccount,
+                                    newAccount);
+                        }
+                    }
+                });
+                popup.show();
+            }
+        };
+        setUpAccountSelector(nameLabel.toString(), onClickListener);
+    }
+
+    private void addRawContactAccountSelector(String nameLabel,
+            final RawContactAccountListAdapter adapter) {
+        final View.OnClickListener onClickListener = new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final ListPopupWindow popup = new ListPopupWindow(getContext(), null);
+                popup.setWidth(mAccountHeaderContainer.getWidth());
+                popup.setAnchorView(mAccountHeaderContainer);
+                popup.setAdapter(adapter);
+                popup.setModal(true);
+                popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+                popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+                    @Override
+                    public void onItemClick(AdapterView<?> parent, View view, int position,
+                                            long id) {
+                        UiClosables.closeQuietly(popup);
+                        final long rawContactId = adapter.getItemId(position);
+                        // Only switch if it's actually a different raw contact.
+                        if (rawContactId != mCurrentRawContactDelta.getRawContactId()
+                                && mListener != null) {
+                            final RawContactDelta rawContactDelta = adapter.getItem(position);
+                            final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
+                                    getContext());
+                            final AccountType accountType = rawContactDelta.getAccountType(
+                                    accountTypes);
+                            final boolean isReadOnly = !accountType.areContactsWritable();
+                            // Reset state.
+                            mIsExpanded = false;
+                            mListener.onRawContactSelected(rawContactId, isReadOnly);
+                        }
+                    }
+                });
+                popup.show();
+            }
+        };
+        setUpAccountSelector(nameLabel, onClickListener);
+    }
+
+    private void setUpAccountSelector(String nameLabel, OnClickListener listener) {
+        addAccountHeader(nameLabel);
+        // Add handlers for choosing another account to save to.
+        mAccountHeaderExpanderIcon.setVisibility(View.VISIBLE);
+        mAccountHeaderContainer.setOnClickListener(listener);
+    }
+
+    private void addPhotoView() {
+        if (!mCurrentRawContactDelta.hasMimeEntries(Photo.CONTENT_ITEM_TYPE)) {
+            wlog("No photo mimetype for this raw contact.");
+            mPhotoView.setVisibility(GONE);
+            return;
+        } else {
+            mPhotoView.setVisibility(VISIBLE);
+        }
+
+        final ValuesDelta superPrimaryDelta = mCurrentRawContactDelta
+                .getSuperPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
+        if (superPrimaryDelta == null) {
+            Log.wtf(TAG, "addPhotoView: no ValueDelta found for current RawContactDelta"
+                    + "that supports a photo.");
+            mPhotoView.setVisibility(GONE);
+            return;
+        }
+        // Set the photo view
+        mPhotoView.setPalette(mMaterialPalette);
+        mPhotoView.setPhoto(superPrimaryDelta);
+
+        if (isReadOnlyRawContact()) {
+            mPhotoView.setReadOnly(true);
+            return;
+        }
+        mPhotoView.setReadOnly(false);
+        mPhotoValuesDelta = superPrimaryDelta;
+    }
+
+    private void addKindSectionViews() {
+        int i = -1;
+
+        for (String mimeType : mSortedMimetypes) {
+            i++;
+            // Ignore mime types that we've already handled
+            if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                vlog("kind: " + i + " " + mimeType + " dropped");
+                continue;
+            }
+            final KindSectionView kindSectionView;
+            final KindSectionData kindSectionData = mKindSectionDataMap.get(mimeType);
+            kindSectionView = inflateKindSectionView(mKindSectionViews, kindSectionData, mimeType);
+            mKindSectionViews.addView(kindSectionView);
+
+            // Keep a pointer to the KindSectionView for each mimeType
+            mKindSectionViewMap.put(mimeType, kindSectionView);
+        }
+    }
+
+    private KindSectionView inflateKindSectionView(ViewGroup viewGroup,
+            KindSectionData kindSectionData, String mimeType) {
+        final KindSectionView kindSectionView = (KindSectionView)
+                mLayoutInflater.inflate(R.layout.item_kind_section, viewGroup,
+                        /* attachToRoot =*/ false);
+        kindSectionView.setIsUserProfile(mIsUserProfile);
+
+        if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)
+                || Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            // Phone numbers and email addresses are always displayed,
+            // even if they are empty
+            kindSectionView.setHideWhenEmpty(false);
+        }
+
+        // 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);
+
+        kindSectionView.setState(kindSectionData, mViewIdGenerator, mListener);
+
+        return kindSectionView;
+    }
+
+    private void showAllFields() {
+        // Stop hiding empty editors and allow the user to enter values for all kinds now
+        for (int i = 0; i < mKindSectionViews.getChildCount(); i++) {
+            final KindSectionView kindSectionView =
+                    (KindSectionView) mKindSectionViews.getChildAt(i);
+            kindSectionView.setHideWhenEmpty(false);
+            kindSectionView.updateEmptyEditors(/* shouldAnimate =*/ true);
+        }
+        mIsExpanded = true;
+
+        // Hide the more fields button
+        mMoreFields.setVisibility(View.GONE);
+    }
+
+    private boolean hasMoreFields() {
+        for (KindSectionView section : mKindSectionViewMap.values()) {
+            if (section.getVisibility() != View.VISIBLE) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static void vlog(String message) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            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);
+    }
+}