Merge "Move Organization up to the top in the Editor"
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 70c88f4..8765a3f 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.os.Bundle;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Intents;
 import android.provider.ContactsContract.RawContacts;
@@ -426,6 +427,24 @@
     }
 
     /**
+     * Compares corresponding fields in values1 and values2. Only the fields
+     * declared by the DataKind are taken into consideration.
+     */
+    protected static boolean areEqual(ValuesDelta values1, ContentValues values2, DataKind kind) {
+        if (kind.fieldList == null) return false;
+
+        for (EditField field : kind.fieldList) {
+            final String value1 = values1.getAsString(field.column);
+            final String value2 = values2.getAsString(field.column);
+            if (!TextUtils.equals(value1, value2)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
      * Parse the given {@link Bundle} into the given {@link EntityDelta} state,
      * assuming the extras defined through {@link Intents}.
      */
@@ -517,21 +536,188 @@
         }
 
         // Arbitrary additional data
-        {
-//            ArrayList<ContentValues> values = extras.getParcelableArrayList(Insert.DATA);
-//            if (values != null) {
-//                parseValues(state, values);
-//            }
+        ArrayList<ContentValues> values = extras.getParcelableArrayList(Insert.DATA);
+        if (values != null) {
+            parseValues(state, source, values);
         }
     }
 
-    private static void parseValues(EntityDelta state, ArrayList<ContentValues> values) {
-        for (ContentValues contentValues : values) {
-            String mimeType = contentValues.getAsString(Data.MIMETYPE);
+    private static void parseValues(
+            EntityDelta state, BaseAccountType source, ArrayList<ContentValues> dataValueList) {
+        for (ContentValues values : dataValueList) {
+            String mimeType = values.getAsString(Data.MIMETYPE);
+            if (TextUtils.isEmpty(mimeType)) {
+                Log.e(TAG, "Mimetype is required. Ignoring: " + values);
+                continue;
+            }
+
+            // Won't override the contact name
+            if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                continue;
+            }
+
+            DataKind kind = source.getKindForMimetype(mimeType);
+            if (kind == null) {
+                Log.e(TAG, "Mimetype not supported for account type " + source.accountType
+                        + ". Ignoring: " + values);
+                continue;
+            }
+
+            ValuesDelta entry = ValuesDelta.fromAfter(values);
+            if (isEmpty(entry, kind)) {
+                continue;
+            }
+
+            ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
+
+            if (kind.isList || GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                // Check for duplicates
+                boolean addEntry = true;
+                int count = 0;
+                if (entries != null && entries.size() > 0) {
+                    for (ValuesDelta delta : entries) {
+                        if (!delta.isDelete()) {
+                            if (areEqual(delta, values, kind)) {
+                                addEntry = false;
+                                break;
+                            }
+                            count++;
+                        }
+                    }
+                }
+
+                if (kind.typeOverallMax != -1 && count >= kind.typeOverallMax) {
+                    Log.e(TAG, "Mimetype allows at most " + kind.typeOverallMax
+                            + " entries. Ignoring: " + values);
+                    addEntry = false;
+                }
+
+                if (addEntry) {
+                    addEntry = adjustType(entry, entries, kind);
+                }
+
+                if (addEntry) {
+                    state.addEntry(entry);
+                }
+            } else {
+                // Non-list entries should not be overridden
+                boolean addEntry = true;
+                if (entries != null && entries.size() > 0) {
+                    for (ValuesDelta delta : entries) {
+                        if (!delta.isDelete() && !isEmpty(delta, kind)) {
+                            addEntry = false;
+                            break;
+                        }
+                    }
+                    if (addEntry) {
+                        for (ValuesDelta delta : entries) {
+                            delta.markDeleted();
+                        }
+                    }
+                }
+
+                if (addEntry) {
+                    addEntry = adjustType(entry, entries, kind);
+                }
+
+                if (addEntry) {
+                    state.addEntry(entry);
+                } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)){
+                    // Note is most likely to contain large amounts of text
+                    // that we don't want to drop on the ground.
+                    for (ValuesDelta delta : entries) {
+                        if (!isEmpty(delta, kind)) {
+                            delta.put(Note.NOTE, delta.getAsString(Note.NOTE) + "\n"
+                                    + values.getAsString(Note.NOTE));
+                            break;
+                        }
+                    }
+                } else {
+                    Log.e(TAG, "Will not override mimetype " + mimeType + ". Ignoring: "
+                            + values);
+                }
+            }
         }
     }
 
     /**
+     * Checks if the data kind allows addition of another entry (e.g. Exchange only
+     * supports two "work" phone numbers).  If not, tries to switch to one of the
+     * unused types.  If successful, returns true.
+     */
+    private static boolean adjustType(
+            ValuesDelta entry, ArrayList<ValuesDelta> entries, DataKind kind) {
+        if (kind.typeColumn == null || kind.typeList == null || kind.typeList.size() == 0) {
+            return true;
+        }
+
+        Integer typeInteger = entry.getAsInteger(kind.typeColumn);
+        int type = typeInteger != null ? typeInteger : kind.typeList.get(0).rawValue;
+
+        if (isTypeAllowed(type, entries, kind)) {
+            entry.put(kind.typeColumn, type);
+            return true;
+        }
+
+        // Specified type is not allowed - choose the first available type that is allowed
+        int size = kind.typeList.size();
+        for (int i = 0; i < size; i++) {
+            EditType editType = kind.typeList.get(i);
+            if (isTypeAllowed(editType.rawValue, entries, kind)) {
+                entry.put(kind.typeColumn, editType.rawValue);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks if a new entry of the specified type can be added to the raw
+     * contact. For example, Exchange only supports two "work" phone numbers, so
+     * addition of a third would not be allowed.
+     */
+    private static boolean isTypeAllowed(int type, ArrayList<ValuesDelta> entries, DataKind kind) {
+        int max = 0;
+        int size = kind.typeList.size();
+        for (int i = 0; i < size; i++) {
+            EditType editType = kind.typeList.get(i);
+            if (editType.rawValue == type) {
+                max = editType.specificMax;
+                break;
+            }
+        }
+
+        if (max == 0) {
+            // This type is not allowed at all
+            return false;
+        }
+
+        if (max == -1) {
+            // Unlimited instances of this type are allowed
+            return true;
+        }
+
+        return getEntryCountByType(entries, kind.typeColumn, type) < max;
+    }
+
+    /**
+     * Counts occurrences of the specified type in the supplied entry list.
+     */
+    private static int getEntryCountByType(
+            ArrayList<ValuesDelta> entries, String typeColumn, int type) {
+        int count = 0;
+        int size = entries.size();
+        for (int i = 0; i < size; i++) {
+            Integer typeInteger = entries.get(i).getAsInteger(typeColumn);
+            if (typeInteger != null && typeInteger == type) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
      * Attempt to parse legacy {@link Insert#IM_PROTOCOL} values, replacing them
      * with updated values.
      */
diff --git a/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java
index 9b95aee..79131d6 100644
--- a/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java
+++ b/src/com/android/contacts/views/editor/AggregationSuggestionEngine.java
@@ -125,7 +125,10 @@
     }
 
     public void setContactId(long contactId) {
-        mContactId = contactId;
+        if (contactId != mContactId) {
+            mContactId = contactId;
+            reset();
+        }
     }
 
     public void setListener(Listener listener) {
diff --git a/src/com/android/contacts/views/editor/ContactEditorFragment.java b/src/com/android/contacts/views/editor/ContactEditorFragment.java
index 46f7d38..3efca45 100644
--- a/src/com/android/contacts/views/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/views/editor/ContactEditorFragment.java
@@ -343,17 +343,21 @@
         mState = EntityDeltaList.fromIterator(entities.iterator());
 
         // Merge in Extras from Intent
-        final boolean hasExtras = mIntentExtras != null && mIntentExtras.size() > 0;
-        final boolean hasState = mState.size() > 0;
-        if (hasExtras && hasState) {
-            // Find source defining the first RawContact found
-            final EntityDelta state = mState.get(0);
-            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+        if (mIntentExtras != null && mIntentExtras.size() > 0) {
             final AccountTypes sources = AccountTypes.getInstance(mContext);
-            final BaseAccountType source = sources.getInflatedSource(accountType,
-                    BaseAccountType.LEVEL_CONSTRAINTS);
-            EntityModifier.parseExtras(mContext, source, state, mIntentExtras);
+            for (EntityDelta state : mState) {
+                final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+                final BaseAccountType source = sources.getInflatedSource(accountType,
+                        BaseAccountType.LEVEL_CONSTRAINTS);
+                if (!source.readOnly) {
+                    // Apply extras to the first writable raw contact only
+                    EntityModifier.parseExtras(mContext, source, state, mIntentExtras);
+                    mIntentExtras = null;
+                    break;
+                }
+            }
         }
+
         bindEditors();
     }
 
@@ -1146,11 +1150,12 @@
 
         if (mAggregationSuggestionEngine == null) {
             mAggregationSuggestionEngine = new AggregationSuggestionEngine(getActivity());
-            mAggregationSuggestionEngine.setContactId(getContactId());
             mAggregationSuggestionEngine.setListener(this);
             mAggregationSuggestionEngine.start();
         }
 
+        mAggregationSuggestionEngine.setContactId(getContactId());
+
         FieldEditorView nameEditor = rawContactEditor.getNameEditor();
         mAggregationSuggestionEngine.onNameChange(nameEditor.getValues());
     }
@@ -1217,14 +1222,14 @@
                     mState = null;
 
                     // Load the suggested one
-                    load(Intent.ACTION_EDIT, contactLookupUri, Contacts.CONTENT_TYPE, null);
-                    mStatus = Status.LOADING;
                     Bundle extras = null;
                     if (values.size() != 0) {
                         extras = new Bundle();
-//                        extras.putParcelableArrayList(Insert.DATA, values);
+                        extras.putParcelableArrayList(Insert.DATA, values);
                     }
-                    getLoaderManager().restartLoader(LOADER_DATA, extras, mDataLoaderListener);
+                    load(Intent.ACTION_EDIT, contactLookupUri, Contacts.CONTENT_TYPE, extras);
+                    mStatus = Status.LOADING;
+                    getLoaderManager().restartLoader(LOADER_DATA, null, mDataLoaderListener);
                 }
             });
             suggestionView.bindSuggestion(suggestion);