Ignore new raw contact display names when saving contacts

Now that we have logic to ignore the display name
we add to newly created raw contacts (when the
user wants to edit a read-only contact) when
determining whether the user made any edits,
we can enable changing the display on the compact
editor again.

Note, there is one glitch -- the name disappears
on the compact editor after you click more fields
(but only when editing read-only contacts).

Tested scenarios:

1) Edit read-only contact, more fields, back, back

   No bogus contact is created and the read-only
   name is displayed on both editors.

2) Edit read-only contact, change name on compact
   editor, back, re-edit read-only contact, and split

   The read-only contact is unchanged and a new
   contact with the edited name is created.

3) Edit read-only contact, more fields, change name on
   full editor, back, back, re-edit read-only contact,
   and split

   Same result as (2)

4) Edit read-only contact, add phone on compact
   editor, back, re-edit read-only contact, and split

   The read-only contact is unchanged and a new
   contact with the read-only name and phone number is
   created.

5) Edit read-only contact, more fields, add phone on full
   editor, back, back, re-edit read-only contact, and split

   Same result as (4)

Bug 21858251
Bug 21464081

Change-Id: I9028fee38b8ea7569968654a756eb98025318e69
diff --git a/src/com/android/contacts/editor/CompactContactEditorFragment.java b/src/com/android/contacts/editor/CompactContactEditorFragment.java
index 9eecaab..23d5bb3 100644
--- a/src/com/android/contacts/editor/CompactContactEditorFragment.java
+++ b/src/com/android/contacts/editor/CompactContactEditorFragment.java
@@ -36,8 +36,6 @@
 import android.os.Bundle;
 import android.util.Log;
 import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
@@ -201,7 +199,11 @@
         // Add input fields for the loaded Contact
         final CompactRawContactsEditorView editorView = getContent();
         editorView.setListener(this);
-        editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId, mNameId);
+        editorView.setState(mState, getMaterialPalette(), mViewIdGenerator, mPhotoId, mNameId,
+                mReadOnlyDisplayName);
+        if (mReadOnlyDisplayName != null) {
+            mReadOnlyNameEditorView = editorView.getDefaultNameEditorView();
+        }
 
         // Set up the photo widget
         mPhotoHandler = createPhotoHandler();
diff --git a/src/com/android/contacts/editor/CompactRawContactsEditorView.java b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
index 669e400..49cd2a2 100644
--- a/src/com/android/contacts/editor/CompactRawContactsEditorView.java
+++ b/src/com/android/contacts/editor/CompactRawContactsEditorView.java
@@ -137,6 +137,8 @@
 
     private long mPhotoRawContactId;
 
+    private StructuredNameEditorView mDefaultNameEditorView;
+
     public CompactRawContactsEditorView(Context context) {
         super(context);
     }
@@ -233,6 +235,10 @@
         return mPhotoRawContactId;
     }
 
+    public StructuredNameEditorView getDefaultNameEditorView() {
+        return mDefaultNameEditorView;
+    }
+
     public StructuredNameEditorView getStructuredNameEditorView() {
         // We only ever show one StructuredName
         return mNames.getChildCount() == 0
@@ -254,9 +260,13 @@
         return mNames.getChildAt(0).findViewById(R.id.anchor_view);
     }
 
+    /**
+     * @param readOnlyDisplayName The display name to set on the new raw contact created in order
+     *         to edit a read-only contact.
+     */
     public void setState(RawContactDeltaList rawContactDeltas,
-            MaterialColorMapUtils.MaterialPalette materialPalette,
-            ViewIdGenerator viewIdGenerator, long photoId, long nameId) {
+            MaterialColorMapUtils.MaterialPalette materialPalette, ViewIdGenerator viewIdGenerator,
+            long photoId, long nameId, String readOnlyDisplayName) {
         mNames.removeAllViews();
         mPhoneticNames.removeAllViews();
         mNicknames.removeAllViews();
@@ -275,7 +285,7 @@
 
         vlog("Setting compact editor state from " + rawContactDeltas);
         addPhotoView(rawContactDeltas, viewIdGenerator, photoId);
-        addStructuredNameView(rawContactDeltas, nameId);
+        addStructuredNameView(rawContactDeltas, nameId, readOnlyDisplayName);
         addEditorViews(rawContactDeltas);
         removeExtraEmptyTextFields(mPhoneNumbers);
         removeExtraEmptyTextFields(mEmails);
@@ -364,7 +374,42 @@
         mPhoto.setVisibility(View.GONE);
     }
 
-    private void addStructuredNameView(RawContactDeltaList rawContactDeltas, long nameId) {
+    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);
+
+                // Make sure we have a structured name
+                RawContactModifier.ensureKindExists(
+                        rawContactDelta, accountType, StructuredName.CONTENT_ITEM_TYPE);
+
+                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;
+                        }
+                    }
+                }
+            }
+        }
+
         // Look for a match for the name ID that was passed in
         for (RawContactDelta rawContactDelta : rawContactDeltas) {
             if (!rawContactDelta.isVisible()) continue;
diff --git a/src/com/android/contacts/editor/ContactEditorBaseFragment.java b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
index a103fd3..e262cee 100644
--- a/src/com/android/contacts/editor/ContactEditorBaseFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorBaseFragment.java
@@ -378,7 +378,11 @@
     //
 
     // Used to pre-populate the editor with a display name when a user edits a read-only contact.
-    protected String mDefaultDisplayName;
+    protected String mReadOnlyDisplayName;
+
+    // The name editor view for the new raw contact that was created so that the user can
+    // edit a read-only contact (to which the new raw contact was joined)
+    protected StructuredNameEditorView mReadOnlyNameEditorView;
 
     /**
      * The contact data loader listener.
@@ -917,7 +921,35 @@
 
         mStatus = Status.SAVING;
 
-        if (!hasPendingChanges()) {
+        // Determine if changes were made in the editor that need to be saved
+        // See go/editing-read-only-contacts
+        boolean hasPendingChanges;
+        if (mReadOnlyNameEditorView == null || mReadOnlyDisplayName == null) {
+            hasPendingChanges = hasPendingChanges();
+        } else {
+            // We created a new raw contact delta with a default display name. We must test for
+            // pending changes while ignoring the default display name.
+            final String displayName = mReadOnlyNameEditorView.getDisplayName();
+            if (mReadOnlyDisplayName.equals(displayName)) {
+                // The user did not modify the default display name, erase it and
+                // check if the user made any other changes
+                mReadOnlyNameEditorView.setDisplayName(null);
+                if (hasPendingChanges()) {
+                    // Other changes were made to the aggregate contact, restore
+                    // the display name and proceed.
+                    mReadOnlyNameEditorView.setDisplayName(displayName);
+                    hasPendingChanges = true;
+                } else {
+                    // No other changes were made to the aggregate contact. Don't add back
+                    // the displayName so that a "bogus" contact is not created.
+                    hasPendingChanges = false;
+                }
+            } else {
+                hasPendingChanges = true;
+            }
+        }
+
+        if (!hasPendingChanges) {
             if (mLookupUri == null && saveMode == SaveMode.RELOAD) {
                 // We don't have anything to save and there isn't even an existing contact yet.
                 // Nothing to do, simply go back to editing mode
@@ -1120,7 +1152,7 @@
             }
         }
 
-        String displayName = null;
+        String readOnlyDisplayName = null;
         // Check for writable raw contacts.  If there are none, then we need to create one so user
         // can edit.  For the user profile case, there is already an editable contact.
         if (!contact.isUserProfile() && !contact.isWritableContact(mContext)) {
@@ -1128,12 +1160,13 @@
 
             // This is potentially an asynchronous call and will add deltas to list.
             selectAccountAndCreateContact();
-            displayName = contact.getDisplayName();
+
+            readOnlyDisplayName = contact.getDisplayName();
         }
 
-        // This also adds deltas to list
-        // If displayName is null at this point it is simply ignored later on by the editor.
-        setStateForExistingContact(displayName, contact.isUserProfile(), mRawContacts);
+        // This also adds deltas to list.  If readOnlyDisplayName is null at this point it is
+        // simply ignored later on by the editor.
+        setStateForExistingContact(readOnlyDisplayName, contact.isUserProfile(), mRawContacts);
     }
 
     /**
@@ -1202,10 +1235,10 @@
     /**
      * Prepare {@link #mState} for an existing contact.
      */
-    protected void setStateForExistingContact(String displayName, boolean isUserProfile,
+    protected void setStateForExistingContact(String readOnlyDisplayName, boolean isUserProfile,
             ImmutableList<RawContact> rawContacts) {
         setEnabled(true);
-        mDefaultDisplayName = displayName;
+        mReadOnlyDisplayName = readOnlyDisplayName;
 
         mState.addAll(rawContacts.iterator());
         setIntentExtras(mIntentExtras);
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 3ad5261..7b17d63 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -169,7 +169,7 @@
             mState = new RawContactDeltaList();
             setStateForNewContact(newAccount, newAccountType, oldState, oldAccountType);
             if (mIsEdit) {
-                setStateForExistingContact(mDefaultDisplayName, mIsUserProfile, mRawContacts);
+                setStateForExistingContact(mReadOnlyDisplayName, mIsUserProfile, mRawContacts);
             }
         }
     }
@@ -293,8 +293,10 @@
 
                 final StructuredNameEditorView nameEditor = rawContactEditor.getNameEditor();
                 nameEditor.setEditorListener(structuredNameListener);
-                if (!TextUtils.isEmpty(mDefaultDisplayName)) {
-                    nameEditor.setDisplayName(mDefaultDisplayName);
+                if (TextUtils.isEmpty(nameEditor.getDisplayName()) &&
+                        !TextUtils.isEmpty(mReadOnlyDisplayName)) {
+                    nameEditor.setDisplayName(mReadOnlyDisplayName);
+                    mReadOnlyNameEditorView = nameEditor;
                 }
 
                 rawContactEditor.setAutoAddToDefaultGroup(mAutoAddToDefaultGroup);