Merge "Clean up un-used ContactListEmptyView."
diff --git a/src/com/android/contacts/activities/AttachPhotoActivity.java b/src/com/android/contacts/activities/AttachPhotoActivity.java
index 54f2de8..1eb77dd 100644
--- a/src/com/android/contacts/activities/AttachPhotoActivity.java
+++ b/src/com/android/contacts/activities/AttachPhotoActivity.java
@@ -39,6 +39,7 @@
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.util.ContactPhotoUtils;
 
 import java.io.File;
@@ -195,7 +196,7 @@
         // the ContactSaveService would not create the new contact, and the
         // full-res photo would fail to be saved to the non-existent contact.
         AccountType account = raw.getRawContactAccountType(this);
-        RawContactDelta.ValuesDelta values =
+        ValuesDelta values =
                 RawContactModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE);
         if (values == null) {
             Log.w(TAG, "cannot attach photo to this account type");
diff --git a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
index f90ae7f..99301c1 100644
--- a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
+++ b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
@@ -64,7 +64,7 @@
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 7240672..71a8cf2 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -86,7 +86,7 @@
 import com.android.contacts.model.Contact;
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
diff --git a/src/com/android/contacts/detail/PhotoSelectionHandler.java b/src/com/android/contacts/detail/PhotoSelectionHandler.java
index ec76379..d004566 100644
--- a/src/com/android/contacts/detail/PhotoSelectionHandler.java
+++ b/src/com/android/contacts/detail/PhotoSelectionHandler.java
@@ -42,7 +42,7 @@
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.util.ContactPhotoUtils;
diff --git a/src/com/android/contacts/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
index 0da05e8..4121d6a 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionEngine.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
@@ -37,7 +37,7 @@
 import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/editor/BaseRawContactEditorView.java b/src/com/android/contacts/editor/BaseRawContactEditorView.java
index 1ff9ac2..48ef963 100644
--- a/src/com/android/contacts/editor/BaseRawContactEditorView.java
+++ b/src/com/android/contacts/editor/BaseRawContactEditorView.java
@@ -29,7 +29,7 @@
 
 import com.android.contacts.R;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountType.EditType;
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 149f821..000a243 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -75,7 +75,7 @@
 import com.android.contacts.model.ContactLoader;
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
diff --git a/src/com/android/contacts/editor/Editor.java b/src/com/android/contacts/editor/Editor.java
index ec816f7..bafffe1 100644
--- a/src/com/android/contacts/editor/Editor.java
+++ b/src/com/android/contacts/editor/Editor.java
@@ -19,7 +19,7 @@
 import android.provider.ContactsContract.Data;
 
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.dataitem.DataKind;
 
 /**
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index 1f6cd43..d2c4e4c 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -30,7 +30,7 @@
 import com.android.contacts.datepicker.DatePickerDialog;
 import com.android.contacts.datepicker.DatePickerDialog.OnDateSetListener;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType.EditField;
 import com.android.contacts.common.model.account.AccountType.EventEditType;
 import com.android.contacts.common.model.dataitem.DataKind;
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index 97257f1..f141011 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -41,7 +41,7 @@
 import com.android.contacts.interactions.GroupCreationDialogFragment;
 import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactModifier;
 import com.google.common.base.Objects;
 
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 4f70cd4..985775e 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -30,7 +30,7 @@
 import com.android.contacts.editor.Editor.EditorListener;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.dataitem.DataKind;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index 91339ae..9b91d0d 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -47,7 +47,7 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType.EditType;
 import com.android.contacts.common.model.dataitem.DataKind;
diff --git a/src/com/android/contacts/editor/PhoneticNameEditorView.java b/src/com/android/contacts/editor/PhoneticNameEditorView.java
index 1ee7e21..43050d9 100644
--- a/src/com/android/contacts/editor/PhoneticNameEditorView.java
+++ b/src/com/android/contacts/editor/PhoneticNameEditorView.java
@@ -22,7 +22,7 @@
 import android.util.AttributeSet;
 
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.model.dataitem.StructuredNameDataItem;
 
diff --git a/src/com/android/contacts/editor/PhotoEditorView.java b/src/com/android/contacts/editor/PhotoEditorView.java
index e106eac..9e20369 100644
--- a/src/com/android/contacts/editor/PhotoEditorView.java
+++ b/src/com/android/contacts/editor/PhotoEditorView.java
@@ -28,7 +28,7 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.util.ContactPhotoUtils;
 
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 27f2ce4..3d1cb85 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -42,7 +42,7 @@
 import com.android.contacts.common.model.account.AccountType.EditType;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactModifier;
 import com.google.common.base.Objects;
 
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index 8f9dcb9..1dc0a39 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -41,7 +41,7 @@
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.model.dataitem.DataKind;
diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java
index fcafe75..e6b9e15 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -26,7 +26,7 @@
 import android.util.AttributeSet;
 
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.dataitem.DataItem;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.model.dataitem.StructuredNameDataItem;
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index 970bbbf..fbaf680 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -38,7 +38,7 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType.EditField;
 import com.android.contacts.common.model.dataitem.DataKind;
 import com.android.contacts.common.util.PhoneNumberFormatter;
diff --git a/src/com/android/contacts/editor/ViewIdGenerator.java b/src/com/android/contacts/editor/ViewIdGenerator.java
index 9f41623..6e26177 100644
--- a/src/com/android/contacts/editor/ViewIdGenerator.java
+++ b/src/com/android/contacts/editor/ViewIdGenerator.java
@@ -21,7 +21,7 @@
 import android.os.Parcelable;
 
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.dataitem.DataKind;
 
 /**
diff --git a/src/com/android/contacts/list/ContactListFilterView.java b/src/com/android/contacts/list/ContactListFilterView.java
index d905683..5fa8e4d 100644
--- a/src/com/android/contacts/list/ContactListFilterView.java
+++ b/src/com/android/contacts/list/ContactListFilterView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 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.
@@ -11,7 +11,7 @@
  * 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.
+ * limitations under the License
  */
 
 package com.android.contacts.list;
diff --git a/src/com/android/contacts/list/CustomContactListFilterActivity.java b/src/com/android/contacts/list/CustomContactListFilterActivity.java
index 9750cf5..034f103 100644
--- a/src/com/android/contacts/list/CustomContactListFilterActivity.java
+++ b/src/com/android/contacts/list/CustomContactListFilterActivity.java
@@ -56,7 +56,7 @@
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
 import com.android.contacts.common.model.AccountTypeManager;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountWithDataSet;
 import com.android.contacts.common.model.account.GoogleAccountType;
diff --git a/src/com/android/contacts/model/RawContactDelta.java b/src/com/android/contacts/model/RawContactDelta.java
index 1a93cfb..34f039d 100644
--- a/src/com/android/contacts/model/RawContactDelta.java
+++ b/src/com/android/contacts/model/RawContactDelta.java
@@ -24,11 +24,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.BaseColumns;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Profile;
 import android.provider.ContactsContract.RawContacts;
@@ -39,13 +34,10 @@
 import com.android.contacts.common.test.NeededForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+
 /**
  * Contains a {@link RawContact} and records any modifications separately so the
  * original {@link RawContact} can be swapped out with a newer version and the
@@ -560,544 +552,4 @@
         }
     };
 
-    /**
-     * Type of {@link ContentValues} that maintains both an original state and a
-     * modified version of that state. This allows us to build insert, update,
-     * or delete operations based on a "before" {@link Entity} snapshot.
-     */
-    public static class ValuesDelta implements Parcelable {
-        protected ContentValues mBefore;
-        protected ContentValues mAfter;
-        protected String mIdColumn = BaseColumns._ID;
-        private boolean mFromTemplate;
-
-        /**
-         * Next value to assign to {@link #mIdColumn} when building an insert
-         * operation through {@link #fromAfter(ContentValues)}. This is used so
-         * we can concretely reference this {@link ValuesDelta} before it has
-         * been persisted.
-         */
-        protected static int sNextInsertId = -1;
-
-        protected ValuesDelta() {
-        }
-
-        /**
-         * Create {@link ValuesDelta}, using the given object as the
-         * "before" state, usually from an {@link Entity}.
-         */
-        public static ValuesDelta fromBefore(ContentValues before) {
-            final ValuesDelta entry = new ValuesDelta();
-            entry.mBefore = before;
-            entry.mAfter = new ContentValues();
-            return entry;
-        }
-
-        /**
-         * Create {@link ValuesDelta}, using the given object as the "after"
-         * state, usually when we are inserting a row instead of updating.
-         */
-        public static ValuesDelta fromAfter(ContentValues after) {
-            final ValuesDelta entry = new ValuesDelta();
-            entry.mBefore = null;
-            entry.mAfter = after;
-
-            // Assign temporary id which is dropped before insert.
-            entry.mAfter.put(entry.mIdColumn, sNextInsertId--);
-            return entry;
-        }
-
-        @NeededForTesting
-        public ContentValues getAfter() {
-            return mAfter;
-        }
-
-        public boolean containsKey(String key) {
-            return ((mAfter != null && mAfter.containsKey(key)) ||
-                    (mBefore != null && mBefore.containsKey(key)));
-        }
-
-        public String getAsString(String key) {
-            if (mAfter != null && mAfter.containsKey(key)) {
-                return mAfter.getAsString(key);
-            } else if (mBefore != null && mBefore.containsKey(key)) {
-                return mBefore.getAsString(key);
-            } else {
-                return null;
-            }
-        }
-
-        public byte[] getAsByteArray(String key) {
-            if (mAfter != null && mAfter.containsKey(key)) {
-                return mAfter.getAsByteArray(key);
-            } else if (mBefore != null && mBefore.containsKey(key)) {
-                return mBefore.getAsByteArray(key);
-            } else {
-                return null;
-            }
-        }
-
-        public Long getAsLong(String key) {
-            if (mAfter != null && mAfter.containsKey(key)) {
-                return mAfter.getAsLong(key);
-            } else if (mBefore != null && mBefore.containsKey(key)) {
-                return mBefore.getAsLong(key);
-            } else {
-                return null;
-            }
-        }
-
-        public Integer getAsInteger(String key) {
-            return getAsInteger(key, null);
-        }
-
-        public Integer getAsInteger(String key, Integer defaultValue) {
-            if (mAfter != null && mAfter.containsKey(key)) {
-                return mAfter.getAsInteger(key);
-            } else if (mBefore != null && mBefore.containsKey(key)) {
-                return mBefore.getAsInteger(key);
-            } else {
-                return defaultValue;
-            }
-        }
-
-        public boolean isChanged(String key) {
-            if (mAfter == null || !mAfter.containsKey(key)) {
-                return false;
-            }
-
-            Object newValue = mAfter.get(key);
-            Object oldValue = mBefore.get(key);
-
-            if (oldValue == null) {
-                return newValue != null;
-            }
-
-            return !oldValue.equals(newValue);
-        }
-
-        public String getMimetype() {
-            return getAsString(Data.MIMETYPE);
-        }
-
-        public Long getId() {
-            return getAsLong(mIdColumn);
-        }
-
-        public void setIdColumn(String idColumn) {
-            mIdColumn = idColumn;
-        }
-
-        public boolean isPrimary() {
-            final Long isPrimary = getAsLong(Data.IS_PRIMARY);
-            return isPrimary == null ? false : isPrimary != 0;
-        }
-
-        public void setFromTemplate(boolean isFromTemplate) {
-            mFromTemplate = isFromTemplate;
-        }
-
-        public boolean isFromTemplate() {
-            return mFromTemplate;
-        }
-
-        public boolean isSuperPrimary() {
-            final Long isSuperPrimary = getAsLong(Data.IS_SUPER_PRIMARY);
-            return isSuperPrimary == null ? false : isSuperPrimary != 0;
-        }
-
-        public boolean beforeExists() {
-            return (mBefore != null && mBefore.containsKey(mIdColumn));
-        }
-
-        /**
-         * When "after" is present, then visible
-         */
-        public boolean isVisible() {
-            return (mAfter != null);
-        }
-
-        /**
-         * When "after" is wiped, action is "delete"
-         */
-        public boolean isDelete() {
-            return beforeExists() && (mAfter == null);
-        }
-
-        /**
-         * When no "before" or "after", is transient
-         */
-        public boolean isTransient() {
-            return (mBefore == null) && (mAfter == null);
-        }
-
-        /**
-         * When "after" has some changes, action is "update"
-         */
-        public boolean isUpdate() {
-            if (!beforeExists() || mAfter == null || mAfter.size() == 0) {
-                return false;
-            }
-            for (String key : mAfter.keySet()) {
-                Object newValue = mAfter.get(key);
-                Object oldValue = mBefore.get(key);
-                if (oldValue == null) {
-                    if (newValue != null) {
-                        return true;
-                    }
-                } else if (!oldValue.equals(newValue)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        /**
-         * When "after" has no changes, action is no-op
-         */
-        public boolean isNoop() {
-            return beforeExists() && (mAfter != null && mAfter.size() == 0);
-        }
-
-        /**
-         * When no "before" id, and has "after", action is "insert"
-         */
-        public boolean isInsert() {
-            return !beforeExists() && (mAfter != null);
-        }
-
-        public void markDeleted() {
-            mAfter = null;
-        }
-
-        /**
-         * Ensure that our internal structure is ready for storing updates.
-         */
-        private void ensureUpdate() {
-            if (mAfter == null) {
-                mAfter = new ContentValues();
-            }
-        }
-
-        public void put(String key, String value) {
-            ensureUpdate();
-            mAfter.put(key, value);
-        }
-
-        public void put(String key, byte[] value) {
-            ensureUpdate();
-            mAfter.put(key, value);
-        }
-
-        public void put(String key, int value) {
-            ensureUpdate();
-            mAfter.put(key, value);
-        }
-
-        public void put(String key, long value) {
-            ensureUpdate();
-            mAfter.put(key, value);
-        }
-
-        public void putNull(String key) {
-            ensureUpdate();
-            mAfter.putNull(key);
-        }
-
-        public void copyStringFrom(ValuesDelta from, String key) {
-            ensureUpdate();
-            put(key, from.getAsString(key));
-        }
-
-        /**
-         * Return set of all keys defined through this object.
-         */
-        public Set<String> keySet() {
-            final HashSet<String> keys = Sets.newHashSet();
-
-            if (mBefore != null) {
-                for (Map.Entry<String, Object> entry : mBefore.valueSet()) {
-                    keys.add(entry.getKey());
-                }
-            }
-
-            if (mAfter != null) {
-                for (Map.Entry<String, Object> entry : mAfter.valueSet()) {
-                    keys.add(entry.getKey());
-                }
-            }
-
-            return keys;
-        }
-
-        /**
-         * Return complete set of "before" and "after" values mixed together,
-         * giving full state regardless of edits.
-         */
-        public ContentValues getCompleteValues() {
-            final ContentValues values = new ContentValues();
-            if (mBefore != null) {
-                values.putAll(mBefore);
-            }
-            if (mAfter != null) {
-                values.putAll(mAfter);
-            }
-            if (values.containsKey(GroupMembership.GROUP_ROW_ID)) {
-                // Clear to avoid double-definitions, and prefer rows
-                values.remove(GroupMembership.GROUP_SOURCE_ID);
-            }
-
-            return values;
-        }
-
-        /**
-         * Merge the "after" values from the given {@link ValuesDelta},
-         * discarding any existing "after" state. This is typically used when
-         * re-parenting changes onto an updated {@link Entity}.
-         */
-        public static ValuesDelta mergeAfter(ValuesDelta local, ValuesDelta remote) {
-            // Bail early if trying to merge delete with missing local
-            if (local == null && (remote.isDelete() || remote.isTransient())) return null;
-
-            // Create local version if none exists yet
-            if (local == null) local = new ValuesDelta();
-
-            if (!local.beforeExists()) {
-                // Any "before" record is missing, so take all values as "insert"
-                local.mAfter = remote.getCompleteValues();
-            } else {
-                // Existing "update" with only "after" values
-                local.mAfter = remote.mAfter;
-            }
-
-            return local;
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (object instanceof ValuesDelta) {
-                // Only exactly equal with both are identical subsets
-                final ValuesDelta other = (ValuesDelta)object;
-                return this.subsetEquals(other) && other.subsetEquals(this);
-            }
-            return false;
-        }
-
-        @Override
-        public String toString() {
-            final StringBuilder builder = new StringBuilder();
-            toString(builder);
-            return builder.toString();
-        }
-
-        /**
-         * Helper for building string representation, leveraging the given
-         * {@link StringBuilder} to minimize allocations.
-         */
-        public void toString(StringBuilder builder) {
-            builder.append("{ ");
-            builder.append("IdColumn=");
-            builder.append(mIdColumn);
-            builder.append(", FromTemplate=");
-            builder.append(mFromTemplate);
-            builder.append(", ");
-            for (String key : this.keySet()) {
-                builder.append(key);
-                builder.append("=");
-                builder.append(this.getAsString(key));
-                builder.append(", ");
-            }
-            builder.append("}");
-        }
-
-        /**
-         * Check if the given {@link ValuesDelta} is both a subset of this
-         * object, and any defined keys have equal values.
-         */
-        public boolean subsetEquals(ValuesDelta other) {
-            for (String key : this.keySet()) {
-                final String ourValue = this.getAsString(key);
-                final String theirValue = other.getAsString(key);
-                if (ourValue == null) {
-                    // If they have value when we're null, no match
-                    if (theirValue != null) return false;
-                } else {
-                    // If both values defined and aren't equal, no match
-                    if (!ourValue.equals(theirValue)) return false;
-                }
-            }
-            // All values compared and matched
-            return true;
-        }
-
-        /**
-         * Build a {@link ContentProviderOperation} that will transform our
-         * "before" state into our "after" state, using insert, update, or
-         * delete as needed.
-         */
-        public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
-            Builder builder = null;
-            if (isInsert()) {
-                // Changed values are "insert" back-referenced to Contact
-                mAfter.remove(mIdColumn);
-                builder = ContentProviderOperation.newInsert(targetUri);
-                builder.withValues(mAfter);
-            } else if (isDelete()) {
-                // When marked for deletion and "before" exists, then "delete"
-                builder = ContentProviderOperation.newDelete(targetUri);
-                builder.withSelection(mIdColumn + "=" + getId(), null);
-            } else if (isUpdate()) {
-                // When has changes and "before" exists, then "update"
-                builder = ContentProviderOperation.newUpdate(targetUri);
-                builder.withSelection(mIdColumn + "=" + getId(), null);
-                builder.withValues(mAfter);
-            }
-            return builder;
-        }
-
-        /** {@inheritDoc} */
-        public int describeContents() {
-            // Nothing special about this parcel
-            return 0;
-        }
-
-        /** {@inheritDoc} */
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeParcelable(mBefore, flags);
-            dest.writeParcelable(mAfter, flags);
-            dest.writeString(mIdColumn);
-        }
-
-        public void readFromParcel(Parcel source) {
-            final ClassLoader loader = getClass().getClassLoader();
-            mBefore = source.<ContentValues> readParcelable(loader);
-            mAfter = source.<ContentValues> readParcelable(loader);
-            mIdColumn = source.readString();
-        }
-
-        public static final Parcelable.Creator<ValuesDelta> CREATOR = new Parcelable.Creator<ValuesDelta>() {
-            public ValuesDelta createFromParcel(Parcel in) {
-                final ValuesDelta values = new ValuesDelta();
-                values.readFromParcel(in);
-                return values;
-            }
-
-            public ValuesDelta[] newArray(int size) {
-                return new ValuesDelta[size];
-            }
-        };
-
-        public void setGroupRowId(long groupId) {
-            put(GroupMembership.GROUP_ROW_ID, groupId);
-        }
-
-        public Long getGroupRowId() {
-            return getAsLong(GroupMembership.GROUP_ROW_ID);
-        }
-
-        public void setPhoto(byte[] value) {
-            put(Photo.PHOTO, value);
-        }
-
-        public byte[] getPhoto() {
-            return getAsByteArray(Photo.PHOTO);
-        }
-
-        public void setSuperPrimary(boolean val) {
-            if (val) {
-                put(Data.IS_SUPER_PRIMARY, 1);
-            } else {
-                put(Data.IS_SUPER_PRIMARY, 0);
-            }
-        }
-
-        public void setPhoneticFamilyName(String value) {
-            put(StructuredName.PHONETIC_FAMILY_NAME, value);
-        }
-
-        public void setPhoneticMiddleName(String value) {
-            put(StructuredName.PHONETIC_MIDDLE_NAME, value);
-        }
-
-        public void setPhoneticGivenName(String value) {
-            put(StructuredName.PHONETIC_GIVEN_NAME, value);
-        }
-
-        public String getPhoneticFamilyName() {
-            return getAsString(StructuredName.PHONETIC_FAMILY_NAME);
-        }
-
-        public String getPhoneticMiddleName() {
-            return getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
-        }
-
-        public String getPhoneticGivenName() {
-            return getAsString(StructuredName.PHONETIC_GIVEN_NAME);
-        }
-
-        public String getDisplayName() {
-            return getAsString(StructuredName.DISPLAY_NAME);
-        }
-
-        public void setDisplayName(String name) {
-            if (name == null) {
-                putNull(StructuredName.DISPLAY_NAME);
-            } else {
-                put(StructuredName.DISPLAY_NAME, name);
-            }
-        }
-
-        public void copyStructuredNameFieldsFrom(ValuesDelta name) {
-            copyStringFrom(name, StructuredName.DISPLAY_NAME);
-
-            copyStringFrom(name, StructuredName.GIVEN_NAME);
-            copyStringFrom(name, StructuredName.FAMILY_NAME);
-            copyStringFrom(name, StructuredName.PREFIX);
-            copyStringFrom(name, StructuredName.MIDDLE_NAME);
-            copyStringFrom(name, StructuredName.SUFFIX);
-
-            copyStringFrom(name, StructuredName.PHONETIC_GIVEN_NAME);
-            copyStringFrom(name, StructuredName.PHONETIC_MIDDLE_NAME);
-            copyStringFrom(name, StructuredName.PHONETIC_FAMILY_NAME);
-
-            copyStringFrom(name, StructuredName.FULL_NAME_STYLE);
-            copyStringFrom(name, StructuredName.PHONETIC_NAME_STYLE);
-        }
-
-        public String getPhoneNumber() {
-            return getAsString(Phone.NUMBER);
-        }
-
-        public String getPhoneNormalizedNumber() {
-            return getAsString(Phone.NORMALIZED_NUMBER);
-        }
-
-        public boolean phoneHasType() {
-            return containsKey(Phone.TYPE);
-        }
-
-        public int getPhoneType() {
-            return getAsInteger(Phone.TYPE);
-        }
-
-        public String getPhoneLabel() {
-            return getAsString(Phone.LABEL);
-        }
-
-        public String getEmailData() {
-            return getAsString(Email.DATA);
-        }
-
-        public boolean emailHasType() {
-            return containsKey(Email.TYPE);
-        }
-
-        public int getEmailType() {
-            return getAsInteger(Email.TYPE);
-        }
-
-        public String getEmailLabel() {
-            return getAsString(Email.LABEL);
-        }
-    }
 }
diff --git a/src/com/android/contacts/model/RawContactDeltaList.java b/src/com/android/contacts/model/RawContactDeltaList.java
index 82dd494..3169c5d 100644
--- a/src/com/android/contacts/model/RawContactDeltaList.java
+++ b/src/com/android/contacts/model/RawContactDeltaList.java
@@ -30,7 +30,6 @@
 import android.provider.ContactsContract.RawContacts;
 import android.util.Log;
 
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/model/RawContactModifier.java b/src/com/android/contacts/model/RawContactModifier.java
index 27162a1..d640f6b 100644
--- a/src/com/android/contacts/model/RawContactModifier.java
+++ b/src/com/android/contacts/model/RawContactModifier.java
@@ -51,7 +51,6 @@
 import com.android.contacts.common.util.CommonDateUtils;
 import com.android.contacts.editor.EventFieldEditorView;
 import com.android.contacts.editor.PhoneticNameEditorView;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
 import com.android.contacts.common.model.account.AccountType;
 import com.android.contacts.common.model.account.AccountType.EditField;
 import com.android.contacts.common.model.account.AccountType.EditType;
diff --git a/src/com/android/contacts/model/ValuesDelta.java b/src/com/android/contacts/model/ValuesDelta.java
new file mode 100644
index 0000000..d9ade77
--- /dev/null
+++ b/src/com/android/contacts/model/ValuesDelta.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2012 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.model;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.provider.BaseColumns;
+import android.provider.ContactsContract;
+
+import com.android.contacts.common.test.NeededForTesting;
+import com.google.common.collect.Sets;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Type of {@link android.content.ContentValues} that maintains both an original state and a
+ * modified version of that state. This allows us to build insert, update,
+ * or delete operations based on a "before" {@link Entity} snapshot.
+ */
+public class ValuesDelta implements Parcelable {
+    protected ContentValues mBefore;
+    protected ContentValues mAfter;
+    protected String mIdColumn = BaseColumns._ID;
+    private boolean mFromTemplate;
+
+    /**
+     * Next value to assign to {@link #mIdColumn} when building an insert
+     * operation through {@link #fromAfter(android.content.ContentValues)}. This is used so
+     * we can concretely reference this {@link ValuesDelta} before it has
+     * been persisted.
+     */
+    protected static int sNextInsertId = -1;
+
+    protected ValuesDelta() {
+    }
+
+    /**
+     * Create {@link ValuesDelta}, using the given object as the
+     * "before" state, usually from an {@link Entity}.
+     */
+    public static ValuesDelta fromBefore(ContentValues before) {
+        final ValuesDelta entry = new ValuesDelta();
+        entry.mBefore = before;
+        entry.mAfter = new ContentValues();
+        return entry;
+    }
+
+    /**
+     * Create {@link ValuesDelta}, using the given object as the "after"
+     * state, usually when we are inserting a row instead of updating.
+     */
+    public static ValuesDelta fromAfter(ContentValues after) {
+        final ValuesDelta entry = new ValuesDelta();
+        entry.mBefore = null;
+        entry.mAfter = after;
+
+        // Assign temporary id which is dropped before insert.
+        entry.mAfter.put(entry.mIdColumn, sNextInsertId--);
+        return entry;
+    }
+
+    @NeededForTesting
+    public ContentValues getAfter() {
+        return mAfter;
+    }
+
+    public boolean containsKey(String key) {
+        return ((mAfter != null && mAfter.containsKey(key)) ||
+                (mBefore != null && mBefore.containsKey(key)));
+    }
+
+    public String getAsString(String key) {
+        if (mAfter != null && mAfter.containsKey(key)) {
+            return mAfter.getAsString(key);
+        } else if (mBefore != null && mBefore.containsKey(key)) {
+            return mBefore.getAsString(key);
+        } else {
+            return null;
+        }
+    }
+
+    public byte[] getAsByteArray(String key) {
+        if (mAfter != null && mAfter.containsKey(key)) {
+            return mAfter.getAsByteArray(key);
+        } else if (mBefore != null && mBefore.containsKey(key)) {
+            return mBefore.getAsByteArray(key);
+        } else {
+            return null;
+        }
+    }
+
+    public Long getAsLong(String key) {
+        if (mAfter != null && mAfter.containsKey(key)) {
+            return mAfter.getAsLong(key);
+        } else if (mBefore != null && mBefore.containsKey(key)) {
+            return mBefore.getAsLong(key);
+        } else {
+            return null;
+        }
+    }
+
+    public Integer getAsInteger(String key) {
+        return getAsInteger(key, null);
+    }
+
+    public Integer getAsInteger(String key, Integer defaultValue) {
+        if (mAfter != null && mAfter.containsKey(key)) {
+            return mAfter.getAsInteger(key);
+        } else if (mBefore != null && mBefore.containsKey(key)) {
+            return mBefore.getAsInteger(key);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    public boolean isChanged(String key) {
+        if (mAfter == null || !mAfter.containsKey(key)) {
+            return false;
+        }
+
+        Object newValue = mAfter.get(key);
+        Object oldValue = mBefore.get(key);
+
+        if (oldValue == null) {
+            return newValue != null;
+        }
+
+        return !oldValue.equals(newValue);
+    }
+
+    public String getMimetype() {
+        return getAsString(ContactsContract.Data.MIMETYPE);
+    }
+
+    public Long getId() {
+        return getAsLong(mIdColumn);
+    }
+
+    public void setIdColumn(String idColumn) {
+        mIdColumn = idColumn;
+    }
+
+    public boolean isPrimary() {
+        final Long isPrimary = getAsLong(ContactsContract.Data.IS_PRIMARY);
+        return isPrimary == null ? false : isPrimary != 0;
+    }
+
+    public void setFromTemplate(boolean isFromTemplate) {
+        mFromTemplate = isFromTemplate;
+    }
+
+    public boolean isFromTemplate() {
+        return mFromTemplate;
+    }
+
+    public boolean isSuperPrimary() {
+        final Long isSuperPrimary = getAsLong(ContactsContract.Data.IS_SUPER_PRIMARY);
+        return isSuperPrimary == null ? false : isSuperPrimary != 0;
+    }
+
+    public boolean beforeExists() {
+        return (mBefore != null && mBefore.containsKey(mIdColumn));
+    }
+
+    /**
+     * When "after" is present, then visible
+     */
+    public boolean isVisible() {
+        return (mAfter != null);
+    }
+
+    /**
+     * When "after" is wiped, action is "delete"
+     */
+    public boolean isDelete() {
+        return beforeExists() && (mAfter == null);
+    }
+
+    /**
+     * When no "before" or "after", is transient
+     */
+    public boolean isTransient() {
+        return (mBefore == null) && (mAfter == null);
+    }
+
+    /**
+     * When "after" has some changes, action is "update"
+     */
+    public boolean isUpdate() {
+        if (!beforeExists() || mAfter == null || mAfter.size() == 0) {
+            return false;
+        }
+        for (String key : mAfter.keySet()) {
+            Object newValue = mAfter.get(key);
+            Object oldValue = mBefore.get(key);
+            if (oldValue == null) {
+                if (newValue != null) {
+                    return true;
+                }
+            } else if (!oldValue.equals(newValue)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * When "after" has no changes, action is no-op
+     */
+    public boolean isNoop() {
+        return beforeExists() && (mAfter != null && mAfter.size() == 0);
+    }
+
+    /**
+     * When no "before" id, and has "after", action is "insert"
+     */
+    public boolean isInsert() {
+        return !beforeExists() && (mAfter != null);
+    }
+
+    public void markDeleted() {
+        mAfter = null;
+    }
+
+    /**
+     * Ensure that our internal structure is ready for storing updates.
+     */
+    private void ensureUpdate() {
+        if (mAfter == null) {
+            mAfter = new ContentValues();
+        }
+    }
+
+    public void put(String key, String value) {
+        ensureUpdate();
+        mAfter.put(key, value);
+    }
+
+    public void put(String key, byte[] value) {
+        ensureUpdate();
+        mAfter.put(key, value);
+    }
+
+    public void put(String key, int value) {
+        ensureUpdate();
+        mAfter.put(key, value);
+    }
+
+    public void put(String key, long value) {
+        ensureUpdate();
+        mAfter.put(key, value);
+    }
+
+    public void putNull(String key) {
+        ensureUpdate();
+        mAfter.putNull(key);
+    }
+
+    public void copyStringFrom(ValuesDelta from, String key) {
+        ensureUpdate();
+        put(key, from.getAsString(key));
+    }
+
+    /**
+     * Return set of all keys defined through this object.
+     */
+    public Set<String> keySet() {
+        final HashSet<String> keys = Sets.newHashSet();
+
+        if (mBefore != null) {
+            for (Map.Entry<String, Object> entry : mBefore.valueSet()) {
+                keys.add(entry.getKey());
+            }
+        }
+
+        if (mAfter != null) {
+            for (Map.Entry<String, Object> entry : mAfter.valueSet()) {
+                keys.add(entry.getKey());
+            }
+        }
+
+        return keys;
+    }
+
+    /**
+     * Return complete set of "before" and "after" values mixed together,
+     * giving full state regardless of edits.
+     */
+    public ContentValues getCompleteValues() {
+        final ContentValues values = new ContentValues();
+        if (mBefore != null) {
+            values.putAll(mBefore);
+        }
+        if (mAfter != null) {
+            values.putAll(mAfter);
+        }
+        if (values.containsKey(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID)) {
+            // Clear to avoid double-definitions, and prefer rows
+            values.remove(ContactsContract.CommonDataKinds.GroupMembership.GROUP_SOURCE_ID);
+        }
+
+        return values;
+    }
+
+    /**
+     * Merge the "after" values from the given {@link ValuesDelta},
+     * discarding any existing "after" state. This is typically used when
+     * re-parenting changes onto an updated {@link Entity}.
+     */
+    public static ValuesDelta mergeAfter(ValuesDelta local, ValuesDelta remote) {
+        // Bail early if trying to merge delete with missing local
+        if (local == null && (remote.isDelete() || remote.isTransient())) return null;
+
+        // Create local version if none exists yet
+        if (local == null) local = new ValuesDelta();
+
+        if (!local.beforeExists()) {
+            // Any "before" record is missing, so take all values as "insert"
+            local.mAfter = remote.getCompleteValues();
+        } else {
+            // Existing "update" with only "after" values
+            local.mAfter = remote.mAfter;
+        }
+
+        return local;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof ValuesDelta) {
+            // Only exactly equal with both are identical subsets
+            final ValuesDelta other = (ValuesDelta)object;
+            return this.subsetEquals(other) && other.subsetEquals(this);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        toString(builder);
+        return builder.toString();
+    }
+
+    /**
+     * Helper for building string representation, leveraging the given
+     * {@link StringBuilder} to minimize allocations.
+     */
+    public void toString(StringBuilder builder) {
+        builder.append("{ ");
+        builder.append("IdColumn=");
+        builder.append(mIdColumn);
+        builder.append(", FromTemplate=");
+        builder.append(mFromTemplate);
+        builder.append(", ");
+        for (String key : this.keySet()) {
+            builder.append(key);
+            builder.append("=");
+            builder.append(this.getAsString(key));
+            builder.append(", ");
+        }
+        builder.append("}");
+    }
+
+    /**
+     * Check if the given {@link ValuesDelta} is both a subset of this
+     * object, and any defined keys have equal values.
+     */
+    public boolean subsetEquals(ValuesDelta other) {
+        for (String key : this.keySet()) {
+            final String ourValue = this.getAsString(key);
+            final String theirValue = other.getAsString(key);
+            if (ourValue == null) {
+                // If they have value when we're null, no match
+                if (theirValue != null) return false;
+            } else {
+                // If both values defined and aren't equal, no match
+                if (!ourValue.equals(theirValue)) return false;
+            }
+        }
+        // All values compared and matched
+        return true;
+    }
+
+    /**
+     * Build a {@link android.content.ContentProviderOperation} that will transform our
+     * "before" state into our "after" state, using insert, update, or
+     * delete as needed.
+     */
+    public ContentProviderOperation.Builder buildDiff(Uri targetUri) {
+        ContentProviderOperation.Builder builder = null;
+        if (isInsert()) {
+            // Changed values are "insert" back-referenced to Contact
+            mAfter.remove(mIdColumn);
+            builder = ContentProviderOperation.newInsert(targetUri);
+            builder.withValues(mAfter);
+        } else if (isDelete()) {
+            // When marked for deletion and "before" exists, then "delete"
+            builder = ContentProviderOperation.newDelete(targetUri);
+            builder.withSelection(mIdColumn + "=" + getId(), null);
+        } else if (isUpdate()) {
+            // When has changes and "before" exists, then "update"
+            builder = ContentProviderOperation.newUpdate(targetUri);
+            builder.withSelection(mIdColumn + "=" + getId(), null);
+            builder.withValues(mAfter);
+        }
+        return builder;
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        // Nothing special about this parcel
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(mBefore, flags);
+        dest.writeParcelable(mAfter, flags);
+        dest.writeString(mIdColumn);
+    }
+
+    public void readFromParcel(Parcel source) {
+        final ClassLoader loader = getClass().getClassLoader();
+        mBefore = source.<ContentValues> readParcelable(loader);
+        mAfter = source.<ContentValues> readParcelable(loader);
+        mIdColumn = source.readString();
+    }
+
+    public static final Creator<ValuesDelta> CREATOR = new Creator<ValuesDelta>() {
+        public ValuesDelta createFromParcel(Parcel in) {
+            final ValuesDelta values = new ValuesDelta();
+            values.readFromParcel(in);
+            return values;
+        }
+
+        public ValuesDelta[] newArray(int size) {
+            return new ValuesDelta[size];
+        }
+    };
+
+    public void setGroupRowId(long groupId) {
+        put(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID, groupId);
+    }
+
+    public Long getGroupRowId() {
+        return getAsLong(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID);
+    }
+
+    public void setPhoto(byte[] value) {
+        put(ContactsContract.CommonDataKinds.Photo.PHOTO, value);
+    }
+
+    public byte[] getPhoto() {
+        return getAsByteArray(ContactsContract.CommonDataKinds.Photo.PHOTO);
+    }
+
+    public void setSuperPrimary(boolean val) {
+        if (val) {
+            put(ContactsContract.Data.IS_SUPER_PRIMARY, 1);
+        } else {
+            put(ContactsContract.Data.IS_SUPER_PRIMARY, 0);
+        }
+    }
+
+    public void setPhoneticFamilyName(String value) {
+        put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME, value);
+    }
+
+    public void setPhoneticMiddleName(String value) {
+        put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME, value);
+    }
+
+    public void setPhoneticGivenName(String value) {
+        put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME, value);
+    }
+
+    public String getPhoneticFamilyName() {
+        return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME);
+    }
+
+    public String getPhoneticMiddleName() {
+        return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME);
+    }
+
+    public String getPhoneticGivenName() {
+        return getAsString(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME);
+    }
+
+    public String getDisplayName() {
+        return getAsString(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
+    }
+
+    public void setDisplayName(String name) {
+        if (name == null) {
+            putNull(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
+        } else {
+            put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+        }
+    }
+
+    public void copyStructuredNameFieldsFrom(ValuesDelta name) {
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME);
+
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PREFIX);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.SUFFIX);
+
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_GIVEN_NAME);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_MIDDLE_NAME);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME);
+
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.FULL_NAME_STYLE);
+        copyStringFrom(name, ContactsContract.CommonDataKinds.StructuredName.PHONETIC_NAME_STYLE);
+    }
+
+    public String getPhoneNumber() {
+        return getAsString(ContactsContract.CommonDataKinds.Phone.NUMBER);
+    }
+
+    public String getPhoneNormalizedNumber() {
+        return getAsString(ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER);
+    }
+
+    public boolean phoneHasType() {
+        return containsKey(ContactsContract.CommonDataKinds.Phone.TYPE);
+    }
+
+    public int getPhoneType() {
+        return getAsInteger(ContactsContract.CommonDataKinds.Phone.TYPE);
+    }
+
+    public String getPhoneLabel() {
+        return getAsString(ContactsContract.CommonDataKinds.Phone.LABEL);
+    }
+
+    public String getEmailData() {
+        return getAsString(ContactsContract.CommonDataKinds.Email.DATA);
+    }
+
+    public boolean emailHasType() {
+        return containsKey(ContactsContract.CommonDataKinds.Email.TYPE);
+    }
+
+    public int getEmailType() {
+        return getAsInteger(ContactsContract.CommonDataKinds.Email.TYPE);
+    }
+
+    public String getEmailLabel() {
+        return getAsString(ContactsContract.CommonDataKinds.Email.LABEL);
+    }
+}
diff --git a/tests/src/com/android/contacts/RawContactDeltaListTests.java b/tests/src/com/android/contacts/RawContactDeltaListTests.java
index ef7667b..a8a5d7b 100644
--- a/tests/src/com/android/contacts/RawContactDeltaListTests.java
+++ b/tests/src/com/android/contacts/RawContactDeltaListTests.java
@@ -37,7 +37,7 @@
 import com.android.contacts.RawContactModifierTests.MockContactsSource;
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
diff --git a/tests/src/com/android/contacts/RawContactDeltaTests.java b/tests/src/com/android/contacts/RawContactDeltaTests.java
index 751d41c..62b7975 100644
--- a/tests/src/com/android/contacts/RawContactDeltaTests.java
+++ b/tests/src/com/android/contacts/RawContactDeltaTests.java
@@ -34,7 +34,7 @@
 
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
@@ -143,49 +143,6 @@
         assertEquals("Unexpected change when merging", source, merged);
     }
 
-    /**
-     * Test that {@link ValuesDelta#buildDiff(android.net.Uri)} is correctly
-     * built for insert, update, and delete cases. Note this only tests behavior
-     * for individual {@link Data} rows.
-     */
-    public void testValuesDiffNone() {
-        final ContentValues before = new ContentValues();
-        before.put(Data._ID, TEST_PHONE_ID);
-        before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
-
-        final ValuesDelta values = ValuesDelta.fromBefore(before);
-
-        // None action shouldn't produce a builder
-        final Builder builder = values.buildDiff(Data.CONTENT_URI);
-        assertNull("None action produced a builder", builder);
-    }
-
-    public void testValuesDiffInsert() {
-        final ContentValues after = new ContentValues();
-        after.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
-
-        final ValuesDelta values = ValuesDelta.fromAfter(after);
-
-        // Should produce an insert action
-        final Builder builder = values.buildDiff(Data.CONTENT_URI);
-        final int type = builder.build().getType();
-        assertEquals("Didn't produce insert action", TYPE_INSERT, type);
-    }
-
-    public void testValuesDiffUpdate() {
-        final ContentValues before = new ContentValues();
-        before.put(Data._ID, TEST_PHONE_ID);
-        before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
-
-        final ValuesDelta values = ValuesDelta.fromBefore(before);
-        values.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
-
-        // Should produce an update action
-        final Builder builder = values.buildDiff(Data.CONTENT_URI);
-        final int type = builder.build().getType();
-        assertEquals("Didn't produce update action", TYPE_UPDATE, type);
-    }
-
     public void testValuesDiffDelete() {
         final ContentValues before = new ContentValues();
         before.put(Data._ID, TEST_PHONE_ID);
diff --git a/tests/src/com/android/contacts/RawContactModifierTests.java b/tests/src/com/android/contacts/RawContactModifierTests.java
index d9f8f36..382735d 100644
--- a/tests/src/com/android/contacts/RawContactModifierTests.java
+++ b/tests/src/com/android/contacts/RawContactModifierTests.java
@@ -41,7 +41,7 @@
 import com.android.contacts.common.model.AccountTypeManager;
 import com.android.contacts.model.RawContact;
 import com.android.contacts.model.RawContactDelta;
-import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.ValuesDelta;
 import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.common.model.account.AccountType;
diff --git a/tests/src/com/android/contacts/ValuesDeltaTests.java b/tests/src/com/android/contacts/ValuesDeltaTests.java
new file mode 100644
index 0000000..e9e4648
--- /dev/null
+++ b/tests/src/com/android/contacts/ValuesDeltaTests.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012 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;
+
+import static android.content.ContentProviderOperation.TYPE_INSERT;
+import static android.content.ContentProviderOperation.TYPE_UPDATE;
+
+import android.content.ContentProviderOperation.Builder;
+import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Data;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.contacts.model.ValuesDelta;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for  {@link ValuesDelta}. These tests
+ * focus on passing changes across {@link android.os.Parcel}, and verifying that they
+ * correctly build expected "diff" operations.
+ */
+@SmallTest
+public class ValuesDeltaTests extends TestCase {
+
+    public static final long TEST_PHONE_ID = 24;
+
+    public static final String TEST_PHONE_NUMBER_1 = "218-555-1111";
+    public static final String TEST_PHONE_NUMBER_2 = "218-555-2222";
+
+    public void testValuesDiffInsert() {
+        final ContentValues after = new ContentValues();
+        after.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+
+        final ValuesDelta values = ValuesDelta.fromAfter(after);
+
+        // Should produce an insert action
+        final Builder builder = values.buildDiff(Data.CONTENT_URI);
+        final int type = builder.build().getType();
+        assertEquals("Didn't produce insert action", TYPE_INSERT, type);
+    }
+
+    /**
+     * Test that {@link ValuesDelta#buildDiff(android.net.Uri)} is correctly
+     * built for insert, update, and delete cases. Note this only tests behavior
+     * for individual {@link Data} rows.
+     */
+    public void testValuesDiffNone() {
+        final ContentValues before = new ContentValues();
+        before.put(Data._ID, TEST_PHONE_ID);
+        before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
+
+        final ValuesDelta values = ValuesDelta.fromBefore(before);
+
+        // None action shouldn't produce a builder
+        final Builder builder = values.buildDiff(Data.CONTENT_URI);
+        assertNull("None action produced a builder", builder);
+    }
+
+    public void testValuesDiffUpdate() {
+        final ContentValues before = new ContentValues();
+        before.put(Data._ID, TEST_PHONE_ID);
+        before.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
+
+        final ValuesDelta values = ValuesDelta.fromBefore(before);
+        values.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
+
+        // Should produce an update action
+        final Builder builder = values.buildDiff(Data.CONTENT_URI);
+        final int type = builder.build().getType();
+        assertEquals("Didn't produce update action", TYPE_UPDATE, type);
+    }
+}