Merge "Added thin object layer around contact data"
diff --git a/src/com/android/contacts/ContactSaveService.java b/src/com/android/contacts/ContactSaveService.java
index 3acc34c..4333aa4 100644
--- a/src/com/android/contacts/ContactSaveService.java
+++ b/src/com/android/contacts/ContactSaveService.java
@@ -47,10 +47,10 @@
 import android.widget.Toast;
 
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDeltaList;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.CallerInfoCacheUtils;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -289,7 +289,7 @@
      * @param rawContactId identifies a writable raw-contact whose photo is to be updated.
      * @param updatedPhotoPath denotes a temporary file containing the contact's new photo.
      */
-    public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
+    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
             String saveModeExtraKey, int saveMode, boolean isProfile,
             Class<? extends Activity> callbackActivity, String callbackAction, long rawContactId,
             String updatedPhotoPath) {
@@ -306,7 +306,7 @@
      * Contact Editor.
      * @param updatedPhotos maps each raw-contact's ID to the file-path of the new photo.
      */
-    public static Intent createSaveContactIntent(Context context, EntityDeltaList state,
+    public static Intent createSaveContactIntent(Context context, RawContactDeltaList state,
             String saveModeExtraKey, int saveMode, boolean isProfile,
             Class<? extends Activity> callbackActivity, String callbackAction,
             Bundle updatedPhotos) {
@@ -332,13 +332,13 @@
     }
 
     private void saveContact(Intent intent) {
-        EntityDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
+        RawContactDeltaList state = intent.getParcelableExtra(EXTRA_CONTACT_STATE);
         boolean isProfile = intent.getBooleanExtra(EXTRA_SAVE_IS_PROFILE, false);
         Bundle updatedPhotos = intent.getParcelableExtra(EXTRA_UPDATED_PHOTOS);
 
         // Trim any empty fields, and RawContacts, before persisting
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(this);
-        EntityModifier.trimEmpty(state, accountTypes);
+        RawContactModifier.trimEmpty(state, accountTypes);
 
         Uri lookupUri = null;
 
@@ -427,16 +427,16 @@
                     throw new IllegalStateException("Version consistency failed for a new contact");
                 }
 
-                final EntityDeltaList newState = EntityDeltaList.fromQuery(
+                final RawContactDeltaList newState = RawContactDeltaList.fromQuery(
                         isProfile
                                 ? RawContactsEntity.PROFILE_CONTENT_URI
                                 : RawContactsEntity.CONTENT_URI,
                         resolver, sb.toString(), null, null);
-                state = EntityDeltaList.mergeAfter(newState, state);
+                state = RawContactDeltaList.mergeAfter(newState, state);
 
                 // Update the new state to use profile URIs if appropriate.
                 if (isProfile) {
-                    for (EntityDelta delta : state) {
+                    for (RawContactDelta delta : state) {
                         delta.setProfileQueryUri();
                     }
                 }
@@ -518,7 +518,7 @@
     /**
      * Find the ID of an existing or newly-inserted raw-contact.  If none exists, return -1.
      */
-    private long getRawContactId(EntityDeltaList state,
+    private long getRawContactId(RawContactDeltaList state,
             final ArrayList<ContentProviderOperation> diff,
             final ContentProviderResult[] results) {
         long existingRawContactId = state.findRawContactId();
diff --git a/src/com/android/contacts/ContactsUtils.java b/src/com/android/contacts/ContactsUtils.java
index 0b63345..e8aa1ae 100644
--- a/src/com/android/contacts/ContactsUtils.java
+++ b/src/com/android/contacts/ContactsUtils.java
@@ -33,9 +33,9 @@
 import android.widget.TextView;
 
 import com.android.contacts.activities.DialtactsActivity;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.test.NeededForTesting;
 import com.android.contacts.util.Constants;
 
diff --git a/src/com/android/contacts/SplitAggregateView.java b/src/com/android/contacts/SplitAggregateView.java
index 834635c..1b42ca3 100644
--- a/src/com/android/contacts/SplitAggregateView.java
+++ b/src/com/android/contacts/SplitAggregateView.java
@@ -35,8 +35,8 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/src/com/android/contacts/TypePrecedence.java b/src/com/android/contacts/TypePrecedence.java
index b5e0777..b2d8a8f 100644
--- a/src/com/android/contacts/TypePrecedence.java
+++ b/src/com/android/contacts/TypePrecedence.java
@@ -23,14 +23,14 @@
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactModifier;
 import com.android.contacts.util.Constants;
 
 /**
  * This class contains utility functions for determining the precedence of
  * different types associated with contact data items.
  *
- * @deprecated use {@link EntityModifier#getTypePrecedence} instead, since this
+ * @deprecated use {@link RawContactModifier#getTypePrecedence} instead, since this
  *             list isn't {@link Account} based.
  */
 @Deprecated
diff --git a/src/com/android/contacts/ViewNotificationService.java b/src/com/android/contacts/ViewNotificationService.java
index 584176d..3bc5ed2 100644
--- a/src/com/android/contacts/ViewNotificationService.java
+++ b/src/com/android/contacts/ViewNotificationService.java
@@ -23,7 +23,9 @@
 import android.os.IBinder;
 import android.util.Log;
 
-import com.android.contacts.ContactLoader.Result;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.ContactLoader;
+
 
 /**
  * Service that sends out a view notification for a contact. At the moment, this is only
@@ -41,9 +43,9 @@
         // We simply need to start a Loader here. When its done, it will send out the
         // View-Notification automatically.
         final ContactLoader contactLoader = new ContactLoader(this, intent.getData(), true);
-        contactLoader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() {
+        contactLoader.registerListener(0, new OnLoadCompleteListener<Contact>() {
             @Override
-            public void onLoadComplete(Loader<Result> loader, Result data) {
+            public void onLoadComplete(Loader<Contact> loader, Contact data) {
                 try {
                     loader.reset();
                 } catch (RuntimeException e) {
diff --git a/src/com/android/contacts/activities/AttachPhotoActivity.java b/src/com/android/contacts/activities/AttachPhotoActivity.java
index 9ca9cfd..2f7651f 100644
--- a/src/com/android/contacts/activities/AttachPhotoActivity.java
+++ b/src/com/android/contacts/activities/AttachPhotoActivity.java
@@ -30,15 +30,15 @@
 import android.provider.ContactsContract.DisplayPhoto;
 import android.util.Log;
 
-import com.android.contacts.ContactLoader;
-import com.android.contacts.ContactLoader.Result;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.ContactsUtils;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.ContactLoader;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDeltaList;
+import com.android.contacts.model.account.AccountType;
 import com.android.contacts.util.ContactPhotoUtils;
 
 import java.io.File;
@@ -131,7 +131,7 @@
         } else if (requestCode == REQUEST_CROP_PHOTO) {
             loadContact(mContactUri, new Listener() {
                 @Override
-                public void onContactLoaded(ContactLoader.Result contact) {
+                public void onContactLoaded(Contact contact) {
                     saveContact(contact);
                 }
             });
@@ -144,10 +144,10 @@
     // instance, the loader doesn't persist across Activity restarts.
     private void loadContact(Uri contactUri, final Listener listener) {
         final ContactLoader loader = new ContactLoader(this, contactUri, true);
-        loader.registerListener(0, new OnLoadCompleteListener<ContactLoader.Result>() {
+        loader.registerListener(0, new OnLoadCompleteListener<Contact>() {
             @Override
             public void onLoadComplete(
-                    Loader<ContactLoader.Result> loader, ContactLoader.Result contact) {
+                    Loader<Contact> loader, Contact contact) {
                 try {
                     loader.reset();
                 }
@@ -161,7 +161,7 @@
     }
 
     private interface Listener {
-        public void onContactLoaded(Result contact);
+        public void onContactLoaded(Contact contact);
     }
 
     /**
@@ -170,11 +170,11 @@
      * - photo has been cropped
      * - contact has been loaded
      */
-    private void saveContact(ContactLoader.Result contact) {
+    private void saveContact(Contact contact) {
 
         // Obtain the raw-contact that we will save to.
-        EntityDeltaList deltaList = contact.createEntityDeltaList();
-        EntityDelta raw = deltaList.getFirstWritableRawContact(this);
+        RawContactDeltaList deltaList = contact.createRawContactDeltaList();
+        RawContactDelta raw = deltaList.getFirstWritableRawContact(this);
         if (raw == null) {
             Log.w(TAG, "no writable raw-contact found");
             return;
@@ -195,13 +195,13 @@
         // 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);
-        EntityDelta.ValuesDelta values =
-                EntityModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE);
+        RawContactDelta.ValuesDelta values =
+                RawContactModifier.ensureKindExists(raw, account, Photo.CONTENT_ITEM_TYPE);
         if (values == null) {
             Log.w(TAG, "cannot attach photo to this account type");
             return;
         }
-        values.put(Photo.PHOTO, compressed);
+        values.setPhoto(compressed);
 
         // Finally, invoke the ContactSaveService.
         Log.v(TAG, "all prerequisites met, about to save photo to contact");
diff --git a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
index 18e360f..c8adf95 100644
--- a/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
+++ b/src/com/android/contacts/activities/ConfirmAddDetailActivity.java
@@ -24,7 +24,6 @@
 import android.content.ContentProviderResult;
 import android.content.ContentResolver;
 import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.OperationApplicationException;
@@ -62,14 +61,15 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.Editor;
 import com.android.contacts.editor.ViewIdGenerator;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContact;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDeltaList;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.EmptyService;
 
@@ -124,11 +124,11 @@
 
     private QueryHandler mQueryHandler;
 
-    /** {@link EntityDeltaList} for the entire selected contact. */
-    private EntityDeltaList mEntityDeltaList;
+    /** {@link RawContactDeltaList} for the entire selected contact. */
+    private RawContactDeltaList mEntityDeltaList;
 
-    /** {@link EntityDeltaList} for the editable account */
-    private EntityDelta mEntityDelta;
+    /** {@link RawContactDeltaList} for the editable account */
+    private RawContactDelta mRawContactDelta;
 
     private String mMimetype = Phone.CONTENT_ITEM_TYPE;
 
@@ -357,7 +357,7 @@
                 new String[] { String.valueOf(mContactId) }, null);
     }
 
-    private static class QueryEntitiesTask extends AsyncTask<Intent, Void, EntityDeltaList> {
+    private static class QueryEntitiesTask extends AsyncTask<Intent, Void, RawContactDeltaList> {
 
         private ConfirmAddDetailActivity activityTarget;
         private String mSelection;
@@ -367,7 +367,7 @@
         }
 
         @Override
-        protected EntityDeltaList doInBackground(Intent... params) {
+        protected RawContactDeltaList doInBackground(Intent... params) {
 
             final Intent intent = params[0];
 
@@ -400,7 +400,7 @@
 
             // Note that this query does not need to concern itself with whether the contact is
             // the user's profile, since the profile does not show up in the picker.
-            return EntityDeltaList.fromQuery(RawContactsEntity.CONTENT_URI,
+            return RawContactDeltaList.fromQuery(RawContactsEntity.CONTENT_URI,
                     activityTarget.getContentResolver(), mSelection,
                     new String[] { selectionArg }, null);
         }
@@ -425,7 +425,7 @@
         }
 
         @Override
-        protected void onPostExecute(EntityDeltaList entityList) {
+        protected void onPostExecute(RawContactDeltaList entityList) {
             if (activityTarget.isFinishing()) {
                 return;
             }
@@ -567,7 +567,7 @@
         }
     }
 
-    private void setEntityDeltaList(EntityDeltaList entityList) {
+    private void setEntityDeltaList(RawContactDeltaList entityList) {
         if (entityList == null) {
             throw new IllegalStateException();
         }
@@ -578,31 +578,33 @@
         mEntityDeltaList = entityList;
 
         // Find the editable raw_contact.
-        mEntityDelta = mEntityDeltaList.getFirstWritableRawContact(this);
+        mRawContactDelta = mEntityDeltaList.getFirstWritableRawContact(this);
 
         // If no editable raw_contacts are found, create one.
-        if (mEntityDelta == null) {
-            mEntityDelta = addEditableRawContact(this, mEntityDeltaList);
+        if (mRawContactDelta == null) {
+            mRawContactDelta = addEditableRawContact(this, mEntityDeltaList);
 
-            if ((mEntityDelta != null) && VERBOSE_LOGGING) {
+            if ((mRawContactDelta != null) && VERBOSE_LOGGING) {
                 Log.v(TAG, "setEntityDeltaList: created editable raw_contact " + entityList);
             }
         }
 
-        if (mEntityDelta == null) {
+        if (mRawContactDelta == null) {
             // Selected contact is read-only, and there's no editable account.
             mIsReadOnly = true;
             mEditableAccountType = null;
         } else {
             mIsReadOnly = false;
 
-            mEditableAccountType = mEntityDelta.getRawContactAccountType(this);
+            mEditableAccountType = mRawContactDelta.getRawContactAccountType(this);
 
             // Handle any incoming values that should be inserted
             final Bundle extras = getIntent().getExtras();
             if (extras != null && extras.size() > 0) {
-                // If there are any intent extras, add them as additional fields in the EntityDelta.
-                EntityModifier.parseExtras(this, mEditableAccountType, mEntityDelta, extras);
+                // If there are any intent extras, add them as additional fields in the
+                // RawContactDelta.
+                RawContactModifier.parseExtras(this, mEditableAccountType, mRawContactDelta,
+                        extras);
             }
         }
 
@@ -610,12 +612,12 @@
     }
 
     /**
-     * Create an {@link EntityDelta} for a raw_contact on the first editable account found, and add
+     * Create an {@link RawContactDelta} for a raw_contact on the first editable account found, and add
      * to the list.  Also copy the structured name from an existing (read-only) raw_contact to the
      * new one, if any of the read-only contacts has a name.
      */
-    private static EntityDelta addEditableRawContact(Context context,
-            EntityDeltaList entityDeltaList) {
+    private static RawContactDelta addEditableRawContact(Context context,
+            RawContactDeltaList entityDeltaList) {
         // First, see if there's an editable account.
         final AccountTypeManager accounts = AccountTypeManager.getInstance(context);
         final List<AccountWithDataSet> editableAccounts = accounts.getAccounts(true);
@@ -627,44 +629,29 @@
         final AccountType accountType = accounts.getAccountType(
                 editableAccount.type, editableAccount.dataSet);
 
-        // Create a new EntityDelta for the new raw_contact.
-        final ContentValues values = new ContentValues();
-        values.put(RawContacts.ACCOUNT_NAME, editableAccount.name);
-        values.put(RawContacts.ACCOUNT_TYPE, editableAccount.type);
-        values.put(RawContacts.DATA_SET, editableAccount.dataSet);
+        // Create a new RawContactDelta for the new raw_contact.
+        final RawContact rawContact = new RawContact(context);
+        rawContact.setAccount(editableAccount);
 
-        final EntityDelta entityDelta = new EntityDelta(ValuesDelta.fromAfter(values));
+        final RawContactDelta entityDelta = new RawContactDelta(ValuesDelta.fromAfter(
+                rawContact.getValues()));
 
         // Then, copy the structure name from an existing (read-only) raw_contact.
-        for (EntityDelta entity : entityDeltaList) {
+        for (RawContactDelta entity : entityDeltaList) {
             final ArrayList<ValuesDelta> readOnlyNames =
                     entity.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
             if ((readOnlyNames != null) && (readOnlyNames.size() > 0)) {
                 final ValuesDelta readOnlyName = readOnlyNames.get(0);
-
-                final ValuesDelta newName = EntityModifier.ensureKindExists(entityDelta,
+                final ValuesDelta newName = RawContactModifier.ensureKindExists(entityDelta,
                         accountType, StructuredName.CONTENT_ITEM_TYPE);
 
                 // Copy all the data fields.
-                newName.copyStringFrom(readOnlyName, StructuredName.DISPLAY_NAME);
-
-                newName.copyStringFrom(readOnlyName, StructuredName.GIVEN_NAME);
-                newName.copyStringFrom(readOnlyName, StructuredName.FAMILY_NAME);
-                newName.copyStringFrom(readOnlyName, StructuredName.PREFIX);
-                newName.copyStringFrom(readOnlyName, StructuredName.MIDDLE_NAME);
-                newName.copyStringFrom(readOnlyName, StructuredName.SUFFIX);
-
-                newName.copyStringFrom(readOnlyName, StructuredName.PHONETIC_GIVEN_NAME);
-                newName.copyStringFrom(readOnlyName, StructuredName.PHONETIC_MIDDLE_NAME);
-                newName.copyStringFrom(readOnlyName, StructuredName.PHONETIC_FAMILY_NAME);
-
-                newName.copyStringFrom(readOnlyName, StructuredName.FULL_NAME_STYLE);
-                newName.copyStringFrom(readOnlyName, StructuredName.PHONETIC_NAME_STYLE);
+                newName.copyStructuredNameFieldsFrom(readOnlyName);
                 break;
             }
         }
 
-        // Add the new EntityDelta to the list.
+        // Add the new RawContactDelta to the list.
         entityDeltaList.add(entityDelta);
 
         return entityDelta;
@@ -695,11 +682,11 @@
             // Skip kind that are not editable
             if (!kind.editable) continue;
             if (mMimetype.equals(kind.mimeType)) {
-                for (ValuesDelta valuesDelta : mEntityDelta.getMimeEntries(mMimetype)) {
+                for (ValuesDelta valuesDelta : mRawContactDelta.getMimeEntries(mMimetype)) {
                     // Skip entries that aren't visible
                     if (!valuesDelta.isVisible()) continue;
                     if (valuesDelta.isInsert()) {
-                        inflateEditorView(kind, valuesDelta, mEntityDelta);
+                        inflateEditorView(kind, valuesDelta, mRawContactDelta);
                         return;
                     }
                 }
@@ -712,7 +699,7 @@
      * the views corresponding to the the object-model. The resulting EditorView is also added
      * to the end of mEditors
      */
-    private void inflateEditorView(DataKind dataKind, ValuesDelta valuesDelta, EntityDelta state) {
+    private void inflateEditorView(DataKind dataKind, ValuesDelta valuesDelta, RawContactDelta state) {
         final View view = mInflater.inflate(dataKind.editorLayoutResourceId, mEditorContainerView,
                 false);
 
@@ -765,11 +752,11 @@
 
     /**
      * Background task for persisting edited contact data, using the changes
-     * defined by a set of {@link EntityDelta}. This task starts
+     * defined by a set of {@link RawContactDelta}. This task starts
      * {@link EmptyService} to make sure the background thread can finish
      * persisting in cases where the system wants to reclaim our process.
      */
-    private static class PersistTask extends AsyncTask<EntityDeltaList, Void, Integer> {
+    private static class PersistTask extends AsyncTask<RawContactDeltaList, Void, Integer> {
         // In the future, use ContactSaver instead of WeakAsyncTask because of
         // the danger of the activity being null during a save action
         private static final int PERSIST_TRIES = 3;
@@ -799,18 +786,18 @@
         }
 
         @Override
-        protected Integer doInBackground(EntityDeltaList... params) {
+        protected Integer doInBackground(RawContactDeltaList... params) {
             final Context context = activityTarget;
             final ContentResolver resolver = context.getContentResolver();
 
-            EntityDeltaList state = params[0];
+            RawContactDeltaList state = params[0];
 
             if (state == null) {
                 return RESULT_FAILURE;
             }
 
             // Trim any empty fields, and RawContacts, before persisting
-            EntityModifier.trimEmpty(state, mAccountTypeManager);
+            RawContactModifier.trimEmpty(state, mAccountTypeManager);
 
             // Attempt to persist changes
             int tries = 0;
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index ace89df..811b904 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -36,7 +36,6 @@
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
@@ -46,7 +45,8 @@
 import com.android.contacts.detail.ContactLoaderFragment;
 import com.android.contacts.detail.ContactLoaderFragment.ContactLoaderFragmentListener;
 import com.android.contacts.interactions.ContactDeletionInteraction;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.PhoneCapabilityTester;
 
 import java.util.ArrayList;
@@ -57,7 +57,7 @@
     /** Shows a toogle button for hiding/showing updates. Don't submit with true */
     private static final boolean DEBUG_TRANSITIONS = false;
 
-    private ContactLoader.Result mContactData;
+    private Contact mContactData;
     private Uri mLookupUri;
 
     private ContactDetailLayoutController mContactDetailLayoutController;
@@ -208,7 +208,7 @@
         }
 
         @Override
-        public void onDetailsLoaded(final ContactLoader.Result result) {
+        public void onDetailsLoaded(final Contact result) {
             if (result == null) {
                 return;
             }
diff --git a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
index 3e2a893..f5852e5 100644
--- a/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorAccountsChangedActivity.java
@@ -31,7 +31,7 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.ContactEditorUtils;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.AccountsListAdapter;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 
diff --git a/src/com/android/contacts/activities/ContactEditorActivity.java b/src/com/android/contacts/activities/ContactEditorActivity.java
index 9639d97..77ed857 100644
--- a/src/com/android/contacts/activities/ContactEditorActivity.java
+++ b/src/com/android/contacts/activities/ContactEditorActivity.java
@@ -36,9 +36,9 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.ContactEditorFragment;
 import com.android.contacts.editor.ContactEditorFragment.SaveMode;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.DialogManager;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/activities/GroupDetailActivity.java b/src/com/android/contacts/activities/GroupDetailActivity.java
index 9b3743f..492a2ff 100644
--- a/src/com/android/contacts/activities/GroupDetailActivity.java
+++ b/src/com/android/contacts/activities/GroupDetailActivity.java
@@ -33,8 +33,8 @@
 import com.android.contacts.R;
 import com.android.contacts.group.GroupDetailDisplayUtils;
 import com.android.contacts.group.GroupDetailFragment;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 
 public class GroupDetailActivity extends ContactsActivity {
 
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 4ee5ea3..a99ac45 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -47,7 +47,6 @@
 import android.view.ViewGroup;
 import android.widget.Toast;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.ContactsUtils;
@@ -81,7 +80,8 @@
 import com.android.contacts.list.OnContactsUnavailableActionListener;
 import com.android.contacts.list.ProviderStatusWatcher;
 import com.android.contacts.list.ProviderStatusWatcher.ProviderStatusListener;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.preference.ContactsPreferenceActivity;
 import com.android.contacts.preference.DisplayOptionsPreferenceFragment;
 import com.android.contacts.util.AccountFilterUtil;
@@ -1162,7 +1162,7 @@
         }
 
         @Override
-        public void onDetailsLoaded(final ContactLoader.Result result) {
+        public void onDetailsLoaded(final Contact result) {
             if (result == null) {
                 // Nothing is loaded. Show empty state.
                 mContactDetailLayoutController.showEmptyState();
diff --git a/src/com/android/contacts/activities/PhotoSelectionActivity.java b/src/com/android/contacts/activities/PhotoSelectionActivity.java
index a5ae7bd..6f3da00 100644
--- a/src/com/android/contacts/activities/PhotoSelectionActivity.java
+++ b/src/com/android/contacts/activities/PhotoSelectionActivity.java
@@ -38,7 +38,7 @@
 import com.android.contacts.R;
 import com.android.contacts.detail.PhotoSelectionHandler;
 import com.android.contacts.editor.PhotoActionPopup;
-import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.util.ContactPhotoUtils;
 import com.android.contacts.util.SchedulingUtils;
 
@@ -93,7 +93,7 @@
     private Uri mPhotoUri;
 
     /** Entity delta list of the contact. */
-    private EntityDeltaList mState;
+    private RawContactDeltaList mState;
 
     /** Whether the contact is the user's profile. */
     private boolean mIsProfile;
@@ -167,7 +167,7 @@
         // Pull data out of the intent.
         final Intent intent = getIntent();
         mPhotoUri = intent.getParcelableExtra(PHOTO_URI);
-        mState = (EntityDeltaList) intent.getParcelableExtra(ENTITY_DELTA_LIST);
+        mState = (RawContactDeltaList) intent.getParcelableExtra(ENTITY_DELTA_LIST);
         mIsProfile = intent.getBooleanExtra(IS_PROFILE, false);
         mIsDirectoryContact = intent.getBooleanExtra(IS_DIRECTORY_CONTACT, false);
         mExpandPhoto = intent.getBooleanExtra(EXPAND_PHOTO, false);
@@ -268,7 +268,7 @@
      * @return An intent that can be used to invoke the photo selection activity.
      */
     public static Intent buildIntent(Context context, Uri photoUri, Bitmap photoBitmap,
-            byte[] photoBytes, Rect photoBounds, EntityDeltaList delta, boolean isProfile,
+            byte[] photoBytes, Rect photoBounds, RawContactDeltaList delta, boolean isProfile,
             boolean isDirectoryContact, boolean expandPhotoOnClick) {
         Intent intent = new Intent(context, PhotoSelectionActivity.class);
         if (photoUri != null && photoBitmap != null && photoBytes != null) {
@@ -515,7 +515,7 @@
         private final PhotoActionListener mListener;
 
         private PhotoHandler(
-                Context context, View photoView, int photoMode, EntityDeltaList state) {
+                Context context, View photoView, int photoMode, RawContactDeltaList state) {
             super(context, photoView, photoMode, PhotoSelectionActivity.this.mIsDirectoryContact,
                     state);
             mListener = new PhotoListener();
@@ -536,7 +536,7 @@
         private final class PhotoListener extends PhotoActionListener {
             @Override
             public void onPhotoSelected(Bitmap bitmap) {
-                EntityDeltaList delta = getDeltaForAttachingPhotoToContact();
+                RawContactDeltaList delta = getDeltaForAttachingPhotoToContact();
                 long rawContactId = getWritableEntityId();
                 final String croppedPath = ContactPhotoUtils.pathForCroppedPhoto(
                         PhotoSelectionActivity.this, mCurrentPhotoFile);
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 67e8e4e..1908e96 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -17,10 +17,7 @@
 package com.android.contacts.detail;
 
 import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
-import android.content.Entity;
-import android.content.Entity.NamedContentValues;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
@@ -28,8 +25,6 @@
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
-import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.StreamItems;
 import android.text.Html;
@@ -44,10 +39,12 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-import com.android.contacts.ContactLoader;
-import com.android.contacts.ContactLoader.Result;
 import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.R;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.RawContact;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.OrganizationDataItem;
 import com.android.contacts.preference.ContactsPreferences;
 import com.android.contacts.util.ContactBadgeUtil;
 import com.android.contacts.util.HtmlUtils;
@@ -55,13 +52,14 @@
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Iterables;
 
 import java.util.List;
 
 /**
  * This class contains utility methods to bind high-level contact details
  * (meaning name, phonetic name, job, and attribution) from a
- * {@link ContactLoader.Result} data object to appropriate {@link View}s.
+ * {@link Contact} data object to appropriate {@link View}s.
  */
 public class ContactDetailDisplayUtils {
     private static final String TAG = "ContactDetailDisplayUtils";
@@ -95,7 +93,7 @@
      * Returns the display name of the contact, using the current display order setting.
      * Returns res/string/missing_name if there is no display name.
      */
-    public static CharSequence getDisplayName(Context context, Result contactData) {
+    public static CharSequence getDisplayName(Context context, Contact contactData) {
         CharSequence displayName = contactData.getDisplayName();
         CharSequence altDisplayName = contactData.getAltDisplayName();
         ContactsPreferences prefs = new ContactsPreferences(context);
@@ -115,7 +113,7 @@
     /**
      * Returns the phonetic name of the contact or null if there isn't one.
      */
-    public static String getPhoneticName(Context context, Result contactData) {
+    public static String getPhoneticName(Context context, Contact contactData) {
         String phoneticName = contactData.getPhoneticName();
         if (!TextUtils.isEmpty(phoneticName)) {
             return phoneticName;
@@ -127,7 +125,7 @@
      * Returns the attribution string for the contact, which may specify the contact directory that
      * the contact came from. Returns null if there is none applicable.
      */
-    public static String getAttribution(Context context, Result contactData) {
+    public static String getAttribution(Context context, Contact contactData) {
         if (contactData.isDirectoryEntry()) {
             String directoryDisplayName = contactData.getDirectoryDisplayName();
             String directoryType = contactData.getDirectoryType();
@@ -143,40 +141,37 @@
      * Returns the organization of the contact. If several organizations are given,
      * the first one is used. Returns null if not applicable.
      */
-    public static String getCompany(Context context, Result contactData) {
+    public static String getCompany(Context context, Contact contactData) {
         final boolean displayNameIsOrganization = contactData.getDisplayNameSource()
                 == DisplayNameSources.ORGANIZATION;
-        for (Entity entity : contactData.getEntities()) {
-            for (NamedContentValues subValue : entity.getSubValues()) {
-                final ContentValues entryValues = subValue.values;
-                final String mimeType = entryValues.getAsString(Data.MIMETYPE);
-
-                if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final String company = entryValues.getAsString(Organization.COMPANY);
-                    final String title = entryValues.getAsString(Organization.TITLE);
-                    final String combined;
-                    // We need to show company and title in a combined string. However, if the
-                    // DisplayName is already the organization, it mirrors company or (if company
-                    // is empty title). Make sure we don't show what's already shown as DisplayName
-                    if (TextUtils.isEmpty(company)) {
-                        combined = displayNameIsOrganization ? null : title;
+        for (RawContact rawContact : contactData.getRawContacts()) {
+            for (DataItem dataItem : Iterables.filter(
+                    rawContact.getDataItems(), OrganizationDataItem.class)) {
+                OrganizationDataItem organization = (OrganizationDataItem) dataItem;
+                final String company = organization.getCompany();
+                final String title = organization.getTitle();
+                final String combined;
+                // We need to show company and title in a combined string. However, if the
+                // DisplayName is already the organization, it mirrors company or (if company
+                // is empty title). Make sure we don't show what's already shown as DisplayName
+                if (TextUtils.isEmpty(company)) {
+                    combined = displayNameIsOrganization ? null : title;
+                } else {
+                    if (TextUtils.isEmpty(title)) {
+                        combined = displayNameIsOrganization ? null : company;
                     } else {
-                        if (TextUtils.isEmpty(title)) {
-                            combined = displayNameIsOrganization ? null : company;
+                        if (displayNameIsOrganization) {
+                            combined = title;
                         } else {
-                            if (displayNameIsOrganization) {
-                                combined = title;
-                            } else {
-                                combined = context.getString(
-                                        R.string.organization_company_and_title,
-                                        company, title);
-                            }
+                            combined = context.getString(
+                                    R.string.organization_company_and_title,
+                                    company, title);
                         }
                     }
+                }
 
-                    if (!TextUtils.isEmpty(combined)) {
-                        return combined;
-                    }
+                if (!TextUtils.isEmpty(combined)) {
+                    return combined;
                 }
             }
         }
@@ -225,7 +220,7 @@
     /**
      * Set the social snippet text. If there isn't one, then set the view to gone.
      */
-    public static void setSocialSnippet(Context context, Result contactData, TextView statusView,
+    public static void setSocialSnippet(Context context, Contact contactData, TextView statusView,
             ImageView statusPhotoView) {
         if (statusView == null) {
             return;
@@ -378,7 +373,7 @@
      * Sets the display name of this contact to the given {@link TextView}. If
      * there is none, then set the view to gone.
      */
-    public static void setDisplayName(Context context, Result contactData, TextView textView) {
+    public static void setDisplayName(Context context, Contact contactData, TextView textView) {
         if (textView == null) {
             return;
         }
@@ -389,7 +384,7 @@
      * Sets the company and job title of this contact to the given {@link TextView}. If
      * there is none, then set the view to gone.
      */
-    public static void setCompanyName(Context context, Result contactData, TextView textView) {
+    public static void setCompanyName(Context context, Contact contactData, TextView textView) {
         if (textView == null) {
             return;
         }
@@ -400,7 +395,7 @@
      * Sets the phonetic name of this contact to the given {@link TextView}. If
      * there is none, then set the view to gone.
      */
-    public static void setPhoneticName(Context context, Result contactData, TextView textView) {
+    public static void setPhoneticName(Context context, Contact contactData, TextView textView) {
         if (textView == null) {
             return;
         }
@@ -411,7 +406,7 @@
      * Sets the attribution contact to the given {@link TextView}. If
      * there is none, then set the view to gone.
      */
-    public static void setAttribution(Context context, Result contactData, TextView textView) {
+    public static void setAttribution(Context context, Contact contactData, TextView textView) {
         if (textView == null) {
             return;
         }
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 4fee26e..f66466d 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -22,8 +22,6 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Entity;
-import android.content.Entity.NamedContentValues;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -36,23 +34,13 @@
 import android.os.ServiceManager;
 import android.provider.ContactsContract;
 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.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.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Directory;
 import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StatusUpdates;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
@@ -83,7 +71,6 @@
 
 import com.android.contacts.Collapser;
 import com.android.contacts.Collapser.Collapsible;
-import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactPresenceIconUtil;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.ContactsUtils;
@@ -92,15 +79,31 @@
 import com.android.contacts.TypePrecedence;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
 import com.android.contacts.editor.SelectAccountDialogFragment;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountType.EditType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
+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.RawContactDeltaList;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.dataitem.EmailDataItem;
+import com.android.contacts.model.dataitem.EventDataItem;
+import com.android.contacts.model.dataitem.GroupMembershipDataItem;
+import com.android.contacts.model.dataitem.ImDataItem;
+import com.android.contacts.model.dataitem.NicknameDataItem;
+import com.android.contacts.model.dataitem.NoteDataItem;
+import com.android.contacts.model.dataitem.OrganizationDataItem;
+import com.android.contacts.model.dataitem.PhoneDataItem;
+import com.android.contacts.model.dataitem.RelationDataItem;
+import com.android.contacts.model.dataitem.SipAddressDataItem;
+import com.android.contacts.model.dataitem.StructuredNameDataItem;
+import com.android.contacts.model.dataitem.StructuredPostalDataItem;
+import com.android.contacts.model.dataitem.WebsiteDataItem;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.util.ClipboardUtils;
 import com.android.contacts.util.Constants;
@@ -110,6 +113,7 @@
 import com.android.contacts.util.StructuredPostalUtils;
 import com.android.internal.telephony.ITelephony;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Iterables;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -137,7 +141,7 @@
     private Uri mLookupUri;
     private Listener mListener;
 
-    private ContactLoader.Result mContactData;
+    private Contact mContactData;
     private ViewGroup mStaticPhotoContainer;
     private View mPhotoTouchOverlay;
     private ListView mListView;
@@ -187,9 +191,8 @@
     private Parcelable mListState;
 
     /**
-     * A list of distinct contact IDs included in the current contact.
+     * Lists of specific types of entries to be shown in contact details.
      */
-    private ArrayList<Long> mRawContactIds = new ArrayList<Long>();
     private ArrayList<DetailViewEntry> mPhoneEntries = new ArrayList<DetailViewEntry>();
     private ArrayList<DetailViewEntry> mSmsEntries = new ArrayList<DetailViewEntry>();
     private ArrayList<DetailViewEntry> mEmailEntries = new ArrayList<DetailViewEntry>();
@@ -335,7 +338,7 @@
         return mListener;
     }
 
-    protected ContactLoader.Result getContactData() {
+    protected Contact getContactData() {
         return mContactData;
     }
 
@@ -362,7 +365,7 @@
         setData(null, null);
     }
 
-    public void setData(Uri lookupUri, ContactLoader.Result result) {
+    public void setData(Uri lookupUri, Contact result) {
         mLookupUri = lookupUri;
         mContactData = result;
         bindData();
@@ -530,65 +533,47 @@
         // Clear out the old entries
         mAllEntries.clear();
 
-        mRawContactIds.clear();
-
         mPrimaryPhoneUri = null;
 
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-
         // Build up method entries
         if (mContactData == null) {
             return;
         }
 
         ArrayList<String> groups = new ArrayList<String>();
-        for (Entity entity: mContactData.getEntities()) {
-            final ContentValues entValues = entity.getEntityValues();
-            final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = entValues.getAsString(RawContacts.DATA_SET);
-            final long rawContactId = entValues.getAsLong(RawContacts._ID);
+        for (RawContact rawContact: mContactData.getRawContacts()) {
+            final long rawContactId = rawContact.getId();
+            for (DataItem dataItem : rawContact.getDataItems()) {
+                dataItem.setRawContactId(rawContactId);
 
-            if (!mRawContactIds.contains(rawContactId)) {
-                mRawContactIds.add(rawContactId);
-            }
+                if (dataItem.getMimeType() == null) continue;
 
-            AccountType type = accountTypes.getAccountType(accountType, dataSet);
-
-            for (NamedContentValues subValue : entity.getSubValues()) {
-                final ContentValues entryValues = subValue.values;
-                entryValues.put(Data.RAW_CONTACT_ID, rawContactId);
-
-                final long dataId = entryValues.getAsLong(Data._ID);
-                final String mimeType = entryValues.getAsString(Data.MIMETYPE);
-                if (mimeType == null) continue;
-
-                if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    Long groupId = entryValues.getAsLong(GroupMembership.GROUP_ROW_ID);
+                if (dataItem instanceof GroupMembershipDataItem) {
+                    GroupMembershipDataItem groupMembership =
+                            (GroupMembershipDataItem) dataItem;
+                    Long groupId = groupMembership.getGroupRowId();
                     if (groupId != null) {
                         handleGroupMembership(groups, mContactData.getGroupMetaData(), groupId);
                     }
                     continue;
                 }
 
-                final DataKind kind = accountTypes.getKindOrFallback(
-                        accountType, dataSet, mimeType);
+                final DataKind kind = dataItem.getDataKind();
                 if (kind == null) continue;
 
-                final DetailViewEntry entry = DetailViewEntry.fromValues(mContext, mimeType, kind,
-                        dataId, entryValues, mContactData.isDirectoryEntry(),
-                        mContactData.getDirectoryId());
+                final DetailViewEntry entry = DetailViewEntry.fromValues(mContext, dataItem,
+                        mContactData.isDirectoryEntry(), mContactData.getDirectoryId());
                 entry.maxLines = kind.maxLinesForDisplay;
 
                 final boolean hasData = !TextUtils.isEmpty(entry.data);
-                Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
-                final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
+                final boolean isSuperPrimary = dataItem.isSuperPrimary();
 
-                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                if (dataItem instanceof StructuredNameDataItem) {
                     // Always ignore the name. It is shown in the header if set
-                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof PhoneDataItem && hasData) {
+                    PhoneDataItem phone = (PhoneDataItem) dataItem;
                     // Build phone entries
-                    String phoneNumberE164 =
-                            entryValues.getAsString(Phone.NORMALIZED_NUMBER);
+                    String phoneNumberE164 = phone.getNormalizedNumber();
                     entry.data = PhoneNumberUtils.formatNumber(
                             entry.data, phoneNumberE164, mDefaultCountryIso);
                     final Intent phoneIntent = mHasPhone ?
@@ -623,7 +608,7 @@
                         // add to end of list
                         mPhoneEntries.add(entry);
                     }
-                } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof EmailDataItem && hasData) {
                     // Build email entries
                     entry.intent = new Intent(Intent.ACTION_SENDTO,
                             Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null));
@@ -638,24 +623,23 @@
                     // When Email rows have status, create additional Im row
                     final DataStatus status = mContactData.getStatuses().get(entry.id);
                     if (status != null) {
-                        final String imMime = Im.CONTENT_ITEM_TYPE;
-                        final DataKind imKind = accountTypes.getKindOrFallback(accountType, dataSet,
-                                imMime);
-                        final DetailViewEntry imEntry = DetailViewEntry.fromValues(mContext, imMime,
-                                imKind, dataId, entryValues, mContactData.isDirectoryEntry(),
-                                mContactData.getDirectoryId());
-                        buildImActions(mContext, imEntry, entryValues);
+                        EmailDataItem email = (EmailDataItem) dataItem;
+                        ImDataItem im = ImDataItem.createFromEmail(email);
+
+                        final DetailViewEntry imEntry = DetailViewEntry.fromValues(mContext, im,
+                                mContactData.isDirectoryEntry(), mContactData.getDirectoryId());
+                        buildImActions(mContext, imEntry, im);
                         imEntry.setPresence(status.getPresence());
-                        imEntry.maxLines = imKind.maxLinesForDisplay;
+                        imEntry.maxLines = kind.maxLinesForDisplay;
                         mImEntries.add(imEntry);
                     }
-                } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof StructuredPostalDataItem && hasData) {
                     // Build postal entries
                     entry.intent = StructuredPostalUtils.getViewPostalAddressIntent(entry.data);
                     mPostalEntries.add(entry);
-                } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof ImDataItem && hasData) {
                     // Build IM entries
-                    buildImActions(mContext, entry, entryValues);
+                    buildImActions(mContext, entry, (ImDataItem) dataItem);
 
                     // Apply presence when available
                     final DataStatus status = mContactData.getStatuses().get(entry.id);
@@ -663,10 +647,10 @@
                         entry.setPresence(status.getPresence());
                     }
                     mImEntries.add(entry);
-                } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
+                } else if (dataItem instanceof OrganizationDataItem) {
                     // Organizations are not shown. The first one is shown in the header
                     // and subsequent ones are not supported anymore
-                } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof NicknameDataItem && hasData) {
                     // Build nickname entries
                     final boolean isNameRawContact =
                         (mContactData.getNameRawContactId() == rawContactId);
@@ -679,11 +663,11 @@
                         entry.uri = null;
                         mNicknameEntries.add(entry);
                     }
-                } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof NoteDataItem && hasData) {
                     // Build note entries
                     entry.uri = null;
                     mNoteEntries.add(entry);
-                } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof WebsiteDataItem && hasData) {
                     // Build Website entries
                     entry.uri = null;
                     try {
@@ -694,7 +678,7 @@
                         Log.e(TAG, "Couldn't parse website: " + entry.data);
                     }
                     mWebsiteEntries.add(entry);
-                } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof SipAddressDataItem && hasData) {
                     // Build SipAddress entries
                     entry.uri = null;
                     if (mHasSip) {
@@ -710,11 +694,11 @@
                     // (Then, we'd also update FallbackAccountType.java to set
                     // secondary=false for this field, and tweak the weight
                     // of its DataKind.)
-                } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof EventDataItem && hasData) {
                     entry.data = DateUtils.formatDate(mContext, entry.data);
                     entry.uri = null;
                     mEventEntries.add(entry);
-                } else if (Relation.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
+                } else if (dataItem instanceof RelationDataItem && hasData) {
                     entry.intent = new Intent(Intent.ACTION_SEARCH);
                     entry.intent.putExtra(SearchManager.QUERY, entry.data);
                     entry.intent.setType(Contacts.CONTENT_TYPE);
@@ -724,14 +708,12 @@
                     entry.intent = new Intent(Intent.ACTION_VIEW);
                     entry.intent.setDataAndType(entry.uri, entry.mimetype);
 
-                    if (kind.actionBody != null) {
-                         CharSequence body = kind.actionBody.inflateUsing(mContext, entryValues);
-                         entry.data = (body == null) ? null : body.toString();
-                    }
+                    entry.data = dataItem.buildDataString();
 
                     if (!TextUtils.isEmpty(entry.data)) {
                         // If the account type exists in the hash map, add it as another entry for
                         // that account type
+                        AccountType type = dataItem.getAccountType();
                         if (mOtherEntriesMap.containsKey(type)) {
                             List<DetailViewEntry> listEntries = mOtherEntriesMap.get(type);
                             listEntries.add(entry);
@@ -960,37 +942,27 @@
         }
     }
 
-    private static String buildDataString(DataKind kind, ContentValues values,
-            Context context) {
-        if (kind.actionBody == null) {
-            return null;
-        }
-        CharSequence actionBody = kind.actionBody.inflateUsing(context, values);
-        return actionBody == null ? null : actionBody.toString();
-    }
-
     /**
      * Writes the Instant Messaging action into the given entry value.
      */
     @VisibleForTesting
     public static void buildImActions(Context context, DetailViewEntry entry,
-            ContentValues values) {
-        final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(values.getAsString(Data.MIMETYPE));
+            ImDataItem im) {
+        final boolean isEmail = im.isCreatedFromEmail();
 
-        if (!isEmail && !isProtocolValid(values)) {
+        if (!isEmail && !im.isProtocolValid()) {
             return;
         }
 
-        final String data = values.getAsString(isEmail ? Email.DATA : Im.DATA);
+        final String data = im.getData();
         if (TextUtils.isEmpty(data)) {
             return;
         }
 
-        final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : values.getAsInteger(Im.PROTOCOL);
+        final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
 
         if (protocol == Im.PROTOCOL_GOOGLE_TALK) {
-            final Integer chatCapabilityObj = values.getAsInteger(Im.CHAT_CAPABILITY);
-            final int chatCapability = chatCapabilityObj == null ? 0 : chatCapabilityObj;
+            final int chatCapability = im.getChatCapability();
             entry.chatCapability = chatCapability;
             entry.typeString = Im.getProtocolLabel(context.getResources(), Im.PROTOCOL_GOOGLE_TALK,
                     null).toString();
@@ -1011,7 +983,7 @@
             }
         } else {
             // Build an IM Intent
-            String host = values.getAsString(Im.CUSTOM_PROTOCOL);
+            String host = im.getCustomProtocol();
 
             if (protocol != Im.PROTOCOL_CUSTOM) {
                 // Try bringing in a well-known host for specific protocols
@@ -1027,19 +999,6 @@
         }
     }
 
-    private static boolean isProtocolValid(ContentValues values) {
-        String protocolString = values.getAsString(Im.PROTOCOL);
-        if (protocolString == null) {
-            return false;
-        }
-        try {
-            Integer.valueOf(protocolString);
-        } catch (NumberFormatException e) {
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Show a list popup.  Used for "popup-able" entry, such as "More networks".
      */
@@ -1246,6 +1205,38 @@
 
         private boolean mIsInSubSection = false;
 
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("== DetailViewEntry ==\n");
+            sb.append("  type: " + type + "\n");
+            sb.append("  kind: " + kind + "\n");
+            sb.append("  typeString: " + typeString + "\n");
+            sb.append("  data: " + data + "\n");
+            sb.append("  uri: " + uri.toString() + "\n");
+            sb.append("  maxLines: " + maxLines + "\n");
+            sb.append("  mimetype: " + mimetype + "\n");
+            sb.append("  isPrimary: " + (isPrimary ? "true" : "false") + "\n");
+            sb.append("  secondaryActionIcon: " + secondaryActionIcon + "\n");
+            sb.append("  secondaryActionDescription: " + secondaryActionDescription + "\n");
+            if (intent == null) {
+                sb.append("  intent: " + intent.toString() + "\n");
+            } else {
+                sb.append("  intent: " + intent.toString() + "\n");
+            }
+            if (secondaryIntent == null) {
+                sb.append("  secondaryIntent: (null)\n");
+            } else {
+                sb.append("  secondaryIntent: " + secondaryIntent.toString() + "\n");
+            }
+            sb.append("  ids: " + Iterables.toString(ids) + "\n");
+            sb.append("  collapseCount: " + collapseCount + "\n");
+            sb.append("  presence: " + presence + "\n");
+            sb.append("  chatCapability: " + chatCapability + "\n");
+            sb.append("  mIsInSubsection: " + (mIsInSubSection ? "true" : "false") + "\n");
+            return sb.toString();
+        }
+
         DetailViewEntry() {
             super(ViewAdapter.VIEW_TYPE_DETAIL_ENTRY);
             isEnabled = true;
@@ -1254,34 +1245,34 @@
         /**
          * Build new {@link DetailViewEntry} and populate from the given values.
          */
-        public static DetailViewEntry fromValues(Context context, String mimeType, DataKind kind,
-                long dataId, ContentValues values, boolean isDirectoryEntry, long directoryId) {
+        public static DetailViewEntry fromValues(Context context, DataItem item,
+                boolean isDirectoryEntry, long directoryId) {
             final DetailViewEntry entry = new DetailViewEntry();
-            entry.id = dataId;
+            entry.id = item.getId();
             entry.context = context;
             entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id);
             if (isDirectoryEntry) {
                 entry.uri = entry.uri.buildUpon().appendQueryParameter(
                         ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId)).build();
             }
-            entry.mimetype = mimeType;
-            entry.kind = (kind.titleRes == -1 || kind.titleRes == 0) ? ""
-                    : context.getString(kind.titleRes);
-            entry.data = buildDataString(kind, values, context);
+            entry.mimetype = item.getMimeType();
+            entry.kind = item.getKindString();
+            entry.data = item.buildDataString();
 
-            if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) {
-                entry.type = values.getAsInteger(kind.typeColumn);
+            if (item.hasKindTypeColumn()) {
+                entry.type = item.getKindTypeColumn();
 
                 // get type string
                 entry.typeString = "";
-                for (EditType type : kind.typeList) {
+                for (EditType type : item.getDataKind().typeList) {
                     if (type.rawValue == entry.type) {
                         if (type.customColumn == null) {
                             // Non-custom type. Get its description from the resource
                             entry.typeString = context.getString(type.labelRes);
                         } else {
                             // Custom type. Read it from the database
-                            entry.typeString = values.getAsString(type.customColumn);
+                            entry.typeString =
+                                    item.getContentValues().getAsString(type.customColumn);
                         }
                         break;
                     }
@@ -1996,7 +1987,7 @@
             if (mContactData.isUserProfile()) return false;
 
             // Only if exactly one raw contact
-            if (mContactData.getEntities().size() != 1) return false;
+            if (mContactData.getRawContacts().size() != 1) return false;
 
             // test if the default group is assigned
             final List<GroupMetaData> groups = mContactData.getGroupMetaData();
@@ -2008,28 +1999,20 @@
             final long defaultGroupId = getDefaultGroupId(groups);
             if (defaultGroupId == -1) return false;
 
-            final Entity rawContactEntity = mContactData.getEntities().get(0);
-            ContentValues rawValues = rawContactEntity.getEntityValues();
-            final String accountType = rawValues.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = rawValues.getAsString(RawContacts.DATA_SET);
-            final AccountTypeManager accountTypes =
-                    AccountTypeManager.getInstance(mContext);
-            final AccountType type = accountTypes.getAccountType(accountType, dataSet);
+            final RawContact rawContact = (RawContact) mContactData.getRawContacts().get(0);
+            final AccountType type = rawContact.getAccountType();
             // Offline or non-writeable account? Nothing to fix
             if (type == null || !type.areContactsWritable()) return false;
 
             // Check whether the contact is in the default group
             boolean isInDefaultGroup = false;
-            for (NamedContentValues subValue : rawContactEntity.getSubValues()) {
-                final String mimeType = subValue.values.getAsString(Data.MIMETYPE);
-
-                if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final Long groupId =
-                            subValue.values.getAsLong(GroupMembership.GROUP_ROW_ID);
-                    if (groupId == defaultGroupId) {
-                        isInDefaultGroup = true;
-                        break;
-                    }
+            for (DataItem dataItem : Iterables.filter(
+                    rawContact.getDataItems(), GroupMembershipDataItem.class)) {
+                GroupMembershipDataItem groupMembership = (GroupMembershipDataItem) dataItem;
+                final Long groupId = groupMembership.getGroupRowId();
+                if (groupId == defaultGroupId) {
+                    isInDefaultGroup = true;
+                    break;
                 }
             }
 
@@ -2049,19 +2032,16 @@
             if (defaultGroupId == -1) return;
 
             // add the group membership to the current state
-            final EntityDeltaList contactDeltaList = mContactData.createEntityDeltaList();
-            final EntityDelta rawContactEntityDelta = contactDeltaList.get(0);
+            final RawContactDeltaList contactDeltaList = mContactData.createRawContactDeltaList();
+            final RawContactDelta rawContactEntityDelta = contactDeltaList.get(0);
 
             final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-            final ValuesDelta values = rawContactEntityDelta.getValues();
-            final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = values.getAsString(RawContacts.DATA_SET);
-            final AccountType type = accountTypes.getAccountType(accountType, dataSet);
+            final AccountType type = rawContactEntityDelta.getAccountType(accountTypes);
             final DataKind groupMembershipKind = type.getKindForMimetype(
                     GroupMembership.CONTENT_ITEM_TYPE);
-            final ValuesDelta entry = EntityModifier.insertChild(rawContactEntityDelta,
+            final ValuesDelta entry = RawContactModifier.insertChild(rawContactEntityDelta,
                     groupMembershipKind);
-            entry.put(GroupMembership.GROUP_ROW_ID, defaultGroupId);
+            entry.setGroupRowId(defaultGroupId);
 
             // and fire off the intent. we don't need a callback, as the database listener
             // should update the ui
@@ -2198,7 +2178,7 @@
         private final LayoutInflater mInflater;
         private final ArrayList<AccountType> mAccountTypes;
 
-        public InvitableAccountTypesAdapter(Context context, ContactLoader.Result contactData) {
+        public InvitableAccountTypesAdapter(Context context, Contact contactData) {
             mContext = context;
             mInflater = LayoutInflater.from(context);
             final List<AccountType> types = contactData.getInvitableAccountTypes();
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index 8a87231..fca426c 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -34,10 +34,10 @@
 import android.widget.AbsListView;
 import android.widget.AbsListView.OnScrollListener;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.NfcHandler;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.model.Contact;
 import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.util.UriUtils;
 import com.android.contacts.widget.FrameLayoutWithOverlay;
@@ -102,7 +102,7 @@
 
     private final ContactDetailFragment.Listener mContactDetailFragmentListener;
 
-    private ContactLoader.Result mContactData;
+    private Contact mContactData;
     private Uri mContactUri;
 
     private boolean mTabCarouselIsAnimating;
@@ -270,7 +270,7 @@
         }
     }
 
-    public void setContactData(ContactLoader.Result data) {
+    public void setContactData(Contact data) {
         final boolean contactWasLoaded;
         final boolean contactHadUpdates;
         final boolean isDifferentContact;
diff --git a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
index 126c43a..eb832e9 100644
--- a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
+++ b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
@@ -25,10 +25,10 @@
 import android.view.View.OnClickListener;
 import android.widget.ImageView;
 
-import com.android.contacts.ContactLoader.Result;
 import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.activities.PhotoSelectionActivity;
-import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.util.ImageViewDrawableSetter;
 
 /**
@@ -36,7 +36,7 @@
  * photo.
  */
 public class ContactDetailPhotoSetter extends ImageViewDrawableSetter {
-    public OnClickListener setupContactPhotoForClick(Context context, Result contactData,
+    public OnClickListener setupContactPhotoForClick(Context context, Contact contactData,
             ImageView photoView, boolean expandPhotoOnClick) {
         setTarget(photoView);
         Bitmap bitmap = setCompressedImage(contactData.getPhotoBinaryData());
@@ -46,12 +46,12 @@
     private static final class PhotoClickListener implements OnClickListener {
 
         private final Context mContext;
-        private final Result mContactData;
+        private final Contact mContactData;
         private final Bitmap mPhotoBitmap;
         private final byte[] mPhotoBytes;
         private final boolean mExpandPhotoOnClick;
 
-        public PhotoClickListener(Context context, Result contactData, Bitmap photoBitmap,
+        public PhotoClickListener(Context context, Contact contactData, Bitmap photoBitmap,
                 byte[] photoBytes, boolean expandPhotoOnClick) {
             mContext = context;
             mContactData = contactData;
@@ -63,7 +63,7 @@
         @Override
         public void onClick(View v) {
             // Assemble the intent.
-            EntityDeltaList delta = mContactData.createEntityDeltaList();
+            RawContactDeltaList delta = mContactData.createRawContactDeltaList();
 
             // Find location and bounds of target view, adjusting based on the
             // assumed local density.
@@ -96,7 +96,7 @@
         }
     }
 
-    private OnClickListener setupClickListener(Context context, Result contactData, Bitmap bitmap,
+    private OnClickListener setupClickListener(Context context, Contact contactData, Bitmap bitmap,
             boolean expandPhotoOnClick) {
         final ImageView target = getTarget();
         if (target == null) return null;
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 3191402..540f001 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -29,8 +29,8 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
+import com.android.contacts.model.Contact;
 import com.android.contacts.util.MoreMath;
 import com.android.contacts.util.SchedulingUtils;
 
@@ -464,7 +464,7 @@
      * Loads the data from the Loader-Result. This is the only function that has to be called
      * from the outside to fully setup the View
      */
-    public void loadData(ContactLoader.Result contactData) {
+    public void loadData(Contact contactData) {
         if (contactData == null) return;
 
         // TODO: Move this into the {@link CarouselTab} class when the updates
diff --git a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
index 9ae614a..88c30c2 100644
--- a/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailUpdatesFragment.java
@@ -28,19 +28,19 @@
 import android.widget.AbsListView.OnScrollListener;
 import android.widget.ListView;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
 import com.android.contacts.detail.ContactDetailDisplayUtils.StreamPhotoTag;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.account.AccountType;
 import com.android.contacts.util.StreamItemEntry;
 
 public class ContactDetailUpdatesFragment extends ListFragment implements FragmentKeyListener {
 
     private static final String TAG = "ContactDetailUpdatesFragment";
 
-    private ContactLoader.Result mContactData;
+    private Contact mContactData;
     private Uri mLookupUri;
 
     private LayoutInflater mInflater;
@@ -121,7 +121,7 @@
         }
     }
 
-    public void setData(Uri lookupUri, ContactLoader.Result result) {
+    public void setData(Uri lookupUri, Contact result) {
         if (result == null) {
             return;
         }
diff --git a/src/com/android/contacts/detail/ContactLoaderFragment.java b/src/com/android/contacts/detail/ContactLoaderFragment.java
index 787390c..6a69744 100644
--- a/src/com/android/contacts/detail/ContactLoaderFragment.java
+++ b/src/com/android/contacts/detail/ContactLoaderFragment.java
@@ -39,12 +39,13 @@
 import android.view.ViewGroup;
 import android.widget.Toast;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
 import com.android.contacts.list.ShortcutIntentBuilder;
 import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.ContactLoader;
 import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.internal.util.Objects;
 
@@ -84,7 +85,7 @@
         /**
          * Contact details have finished loading.
          */
-        public void onDetailsLoaded(ContactLoader.Result result);
+        public void onDetailsLoaded(Contact result);
 
         /**
          * User decided to go to Edit-Mode
@@ -107,7 +108,7 @@
     private Uri mLookupUri;
     private ContactLoaderFragmentListener mListener;
 
-    private ContactLoader.Result mContactData;
+    private Contact mContactData;
 
     public ContactLoaderFragment() {
     }
@@ -179,10 +180,10 @@
     /**
      * The listener for the detail loader
      */
-    private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
-            new LoaderCallbacks<ContactLoader.Result>() {
+    private final LoaderManager.LoaderCallbacks<Contact> mDetailLoaderListener =
+            new LoaderCallbacks<Contact>() {
         @Override
-        public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+        public Loader<Contact> onCreateLoader(int id, Bundle args) {
             Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI);
             return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */,
                     true /* loadStreamItems */, true /* load invitable account types */,
@@ -190,7 +191,7 @@
         }
 
         @Override
-        public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
+        public void onLoadFinished(Loader<Contact> loader, Contact data) {
             if (!mLookupUri.equals(data.getRequestedUri())) {
                 Log.e(TAG, "Different URI: requested=" + mLookupUri + "  actual=" + data);
                 return;
@@ -219,7 +220,7 @@
         }
 
         @Override
-        public void onLoaderReset(Loader<ContactLoader.Result> loader) {}
+        public void onLoaderReset(Loader<Contact> loader) {}
     };
 
     @Override
@@ -461,14 +462,14 @@
 
     /** Toggles whether to load stream items. Just for debugging */
     public void toggleLoadStreamItems() {
-        Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
+        Loader<Contact> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
         ContactLoader loader = (ContactLoader) loaderObj;
         loader.setLoadStreamItems(!loader.getLoadStreamItems());
     }
 
     /** Returns whether to load stream items. Just for debugging */
     public boolean getLoadStreamItems() {
-        Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
+        Loader<Contact> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
         ContactLoader loader = (ContactLoader) loaderObj;
         return loader != null && loader.getLoadStreamItems();
     }
diff --git a/src/com/android/contacts/detail/PhotoSelectionHandler.java b/src/com/android/contacts/detail/PhotoSelectionHandler.java
index 569012e..b5e406a 100644
--- a/src/com/android/contacts/detail/PhotoSelectionHandler.java
+++ b/src/com/android/contacts/detail/PhotoSelectionHandler.java
@@ -39,12 +39,12 @@
 
 import com.android.contacts.R;
 import com.android.contacts.editor.PhotoActionPopup;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.RawContactDeltaList;
 import com.android.contacts.util.ContactPhotoUtils;
 
 import java.io.File;
@@ -64,12 +64,12 @@
     private final View mPhotoView;
     private final int mPhotoMode;
     private final int mPhotoPickSize;
-    private final EntityDeltaList mState;
+    private final RawContactDeltaList mState;
     private final boolean mIsDirectoryContact;
     private ListPopupWindow mPopup;
 
     public PhotoSelectionHandler(Context context, View photoView, int photoMode,
-            boolean isDirectoryContact, EntityDeltaList state) {
+            boolean isDirectoryContact, RawContactDeltaList state) {
         mContext = context;
         mPhotoView = photoView;
         mPhotoMode = photoMode;
@@ -162,12 +162,12 @@
      *     or null if the photo could not be parsed or none of the accounts associated with the
      *     contact are writable.
      */
-    public EntityDeltaList getDeltaForAttachingPhotoToContact() {
+    public RawContactDeltaList getDeltaForAttachingPhotoToContact() {
         // Find the first writable entity.
         int writableEntityIndex = getWritableEntityIndex();
         if (writableEntityIndex != -1) {
             // We are guaranteed to have contact data if we have a writable entity index.
-            final EntityDelta delta = mState.get(writableEntityIndex);
+            final RawContactDelta delta = mState.get(writableEntityIndex);
 
             // Need to find the right account so that EntityModifier knows which fields to add
             final ContentValues entityValues = delta.getValues().getCompleteValues();
@@ -176,10 +176,10 @@
             final AccountType accountType = AccountTypeManager.getInstance(mContext).getAccountType(
                         type, dataSet);
 
-            final ValuesDelta child = EntityModifier.ensureKindExists(
+            final ValuesDelta child = RawContactModifier.ensureKindExists(
                     delta, accountType, Photo.CONTENT_ITEM_TYPE);
             child.setFromTemplate(false);
-            child.put(Photo.IS_SUPER_PRIMARY, 1);
+            child.setSuperPrimary(true);
 
             return mState;
         }
diff --git a/src/com/android/contacts/detail/StreamItemAdapter.java b/src/com/android/contacts/detail/StreamItemAdapter.java
index 7564167..15219cd 100644
--- a/src/com/android/contacts/detail/StreamItemAdapter.java
+++ b/src/com/android/contacts/detail/StreamItemAdapter.java
@@ -23,8 +23,8 @@
 import android.widget.BaseAdapter;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 import com.android.contacts.util.StreamItemEntry;
 import com.google.common.collect.Lists;
 
diff --git a/src/com/android/contacts/editor/AggregationSuggestionEngine.java b/src/com/android/contacts/editor/AggregationSuggestionEngine.java
index 7c0e668..0da05e8 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.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/editor/AggregationSuggestionView.java b/src/com/android/contacts/editor/AggregationSuggestionView.java
index 77b678a..7327a4c 100644
--- a/src/com/android/contacts/editor/AggregationSuggestionView.java
+++ b/src/com/android/contacts/editor/AggregationSuggestionView.java
@@ -28,8 +28,8 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.AggregationSuggestionEngine.RawContact;
 import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 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 65395a8..66fc864 100644
--- a/src/com/android/contacts/editor/BaseRawContactEditorView.java
+++ b/src/com/android/contacts/editor/BaseRawContactEditorView.java
@@ -17,7 +17,6 @@
 package com.android.contacts.editor;
 
 import android.content.Context;
-import android.content.Entity;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.provider.ContactsContract.CommonDataKinds.Photo;
@@ -29,20 +28,20 @@
 import android.widget.LinearLayout;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountType.EditType;
 
 /**
  * Base view that provides common code for the editor interaction for a specific
- * RawContact represented through an {@link EntityDelta}.
+ * RawContact represented through an {@link RawContactDelta}.
  * <p>
  * Internal updates are performed against {@link ValuesDelta} so that the
- * source {@link Entity} can be swapped out. Any state-based changes, such as
+ * source {@link RawContact} can be swapped out. Any state-based changes, such as
  * adding {@link Data} rows or changing {@link EditType}, are performed through
- * {@link EntityModifier} to ensure that {@link AccountType} are enforced.
+ * {@link RawContactModifier} to ensure that {@link AccountType} are enforced.
  */
 public abstract class BaseRawContactEditorView extends LinearLayout {
 
@@ -78,7 +77,7 @@
 
     /**
      * Assign the given {@link Bitmap} to the internal {@link PhotoEditorView}
-     * for the {@link EntityDelta} currently being edited.
+     * for the {@link RawContactDelta} currently being edited.
      */
     public void setPhotoBitmap(Bitmap bitmap) {
         mPhoto.setPhotoBitmap(bitmap);
@@ -115,10 +114,10 @@
 
     /**
      * Set the internal state for this view, given a current
-     * {@link EntityDelta} state and the {@link AccountType} that
+     * {@link RawContactDelta} state and the {@link AccountType} that
      * apply to that state.
      */
-    public abstract void setState(EntityDelta state, AccountType source, ViewIdGenerator vig,
+    public abstract void setState(RawContactDelta state, AccountType source, ViewIdGenerator vig,
             boolean isProfile);
 
     /* package */ void setExpanded(boolean value) {
diff --git a/src/com/android/contacts/editor/ContactEditorFragment.java b/src/com/android/contacts/editor/ContactEditorFragment.java
index 536c41d..780279e 100644
--- a/src/com/android/contacts/editor/ContactEditorFragment.java
+++ b/src/com/android/contacts/editor/ContactEditorFragment.java
@@ -29,7 +29,6 @@
 import android.content.Context;
 import android.content.CursorLoader;
 import android.content.DialogInterface;
-import android.content.Entity;
 import android.content.Intent;
 import android.content.Loader;
 import android.database.Cursor;
@@ -62,7 +61,6 @@
 import android.widget.ListPopupWindow;
 import android.widget.Toast;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
@@ -72,18 +70,22 @@
 import com.android.contacts.detail.PhotoSelectionHandler;
 import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
 import com.android.contacts.editor.Editor.EditorListener;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.GoogleAccountType;
+import com.android.contacts.model.Contact;
+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.RawContactDeltaList;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.GoogleAccountType;
 import com.android.contacts.util.AccountsListAdapter;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.util.ContactPhotoUtils;
 import com.android.contacts.util.HelpUtils;
+import com.google.common.collect.ImmutableList;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -224,7 +226,7 @@
     private ContactEditorUtils mEditorUtils;
 
     private LinearLayout mContent;
-    private EntityDeltaList mState;
+    private RawContactDeltaList mState;
 
     private ViewIdGenerator mViewIdGenerator;
 
@@ -420,7 +422,7 @@
             mViewIdGenerator = new ViewIdGenerator();
         } else {
             // Read state from savedState. No loading involved here
-            mState = savedState.<EntityDeltaList> getParcelable(KEY_EDIT_STATE);
+            mState = savedState.<RawContactDeltaList> getParcelable(KEY_EDIT_STATE);
             mRawContactIdRequestingPhoto = savedState.getLong(
                     KEY_RAW_CONTACT_ID_REQUESTING_PHOTO);
             mViewIdGenerator = savedState.getParcelable(KEY_VIEW_ID_GENERATOR);
@@ -436,7 +438,7 @@
         }
     }
 
-    public void setData(ContactLoader.Result data) {
+    public void setData(Contact data) {
         // If we have already loaded data, we do not want to change it here to not confuse the user
         if (mState != null) {
             Log.v(TAG, "Ignoring background change. This will have to be rebased later");
@@ -444,19 +446,17 @@
         }
 
         // See if this edit operation needs to be redirected to a custom editor
-        ArrayList<Entity> entities = data.getEntities();
-        if (entities.size() == 1) {
-            Entity entity = entities.get(0);
-            ContentValues entityValues = entity.getEntityValues();
-            String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
-            String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
-            AccountType accountType = AccountTypeManager.getInstance(mContext).getAccountType(
-                    type, dataSet);
+        ImmutableList<RawContact> rawContacts = data.getRawContacts();
+        if (rawContacts.size() == 1) {
+            RawContact rawContact = rawContacts.get(0);
+            String type = rawContact.getAccountTypeString();
+            String dataSet = rawContact.getDataSet();
+            AccountType accountType = rawContact.getAccountType();
             if (accountType.getEditContactActivityClassName() != null &&
                     !accountType.areContactsWritable()) {
                 if (mListener != null) {
-                    String name = entityValues.getAsString(RawContacts.ACCOUNT_NAME);
-                    long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
+                    String name = rawContact.getAccountName();
+                    long rawContactId = rawContact.getId();
                     mListener.onCustomEditContactActivityRequested(
                             new AccountWithDataSet(name, type, dataSet),
                             ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
@@ -474,10 +474,10 @@
         mListener.onCustomEditContactActivityRequested(account, uri, null, false);
     }
 
-    private void bindEditorsForExistingContact(ContactLoader.Result contact) {
+    private void bindEditorsForExistingContact(Contact contact) {
         setEnabled(true);
 
-        mState = contact.createEntityDeltaList();
+        mState = contact.createRawContactDeltaList();
         setIntentExtras(mIntentExtras);
         mIntentExtras = null;
 
@@ -486,7 +486,7 @@
         boolean localProfileExists = false;
 
         if (mIsUserProfile) {
-            for (EntityDelta state : mState) {
+            for (RawContactDelta state : mState) {
                 // For profile contacts, we need a different query URI
                 state.setProfileQueryUri();
                 // Try to find a local profile contact
@@ -496,11 +496,11 @@
             }
             // Editor should always present a local profile for editing
             if (!localProfileExists) {
-                final ContentValues values = new ContentValues();
-                values.putNull(RawContacts.ACCOUNT_NAME);
-                values.putNull(RawContacts.ACCOUNT_TYPE);
-                values.putNull(RawContacts.DATA_SET);
-                EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
+                final RawContact rawContact = new RawContact(mContext);
+                rawContact.setAccountToLocal();
+
+                RawContactDelta insert = new RawContactDelta(ValuesDelta.fromAfter(
+                        rawContact.getValues()));
                 insert.setProfileQueryUri();
                 mState.add(insert);
             }
@@ -519,13 +519,11 @@
         }
 
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        for (EntityDelta state : mState) {
-            final String accountType = state.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = state.getValues().getAsString(RawContacts.DATA_SET);
-            final AccountType type = accountTypes.getAccountType(accountType, dataSet);
+        for (RawContactDelta state : mState) {
+            final AccountType type = state.getAccountType(accountTypes);
             if (type.areContactsWritable()) {
                 // Apply extras to the first writable raw contact only
-                EntityModifier.parseExtras(mContext, type, state, extras);
+                RawContactModifier.parseExtras(mContext, type, state, extras);
                 break;
             }
         }
@@ -604,7 +602,8 @@
      * @param newAccount New account to be used.
      */
     private void rebindEditorsForNewContact(
-            EntityDelta oldState, AccountWithDataSet oldAccount, AccountWithDataSet newAccount) {
+            RawContactDelta oldState, AccountWithDataSet oldAccount,
+            AccountWithDataSet newAccount) {
         AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
         AccountType oldAccountType = accountTypes.getAccountType(
                 oldAccount.type, oldAccount.dataSet);
@@ -628,36 +627,34 @@
     }
 
     private void bindEditorsForNewContact(AccountWithDataSet newAccount,
-            final AccountType newAccountType, EntityDelta oldState, AccountType oldAccountType) {
+            final AccountType newAccountType, RawContactDelta oldState,
+            AccountType oldAccountType) {
         mStatus = Status.EDITING;
 
-        final ContentValues values = new ContentValues();
+        final RawContact rawContact = new RawContact(mContext);
         if (newAccount != null) {
-            values.put(RawContacts.ACCOUNT_NAME, newAccount.name);
-            values.put(RawContacts.ACCOUNT_TYPE, newAccount.type);
-            values.put(RawContacts.DATA_SET, newAccount.dataSet);
+            rawContact.setAccount(newAccount);
         } else {
-            values.putNull(RawContacts.ACCOUNT_NAME);
-            values.putNull(RawContacts.ACCOUNT_TYPE);
-            values.putNull(RawContacts.DATA_SET);
+            rawContact.setAccountToLocal();
         }
 
-        EntityDelta insert = new EntityDelta(ValuesDelta.fromAfter(values));
+        RawContactDelta insert = new RawContactDelta(ValuesDelta.fromAfter(rawContact.getValues()));
         if (oldState == null) {
             // Parse any values from incoming intent
-            EntityModifier.parseExtras(mContext, newAccountType, insert, mIntentExtras);
+            RawContactModifier.parseExtras(mContext, newAccountType, insert, mIntentExtras);
         } else {
-            EntityModifier.migrateStateForNewContact(mContext, oldState, insert,
+            RawContactModifier.migrateStateForNewContact(mContext, oldState, insert,
                     oldAccountType, newAccountType);
         }
 
         // Ensure we have some default fields (if the account type does not support a field,
         // ensureKind will not add it, so it is safe to add e.g. Event)
-        EntityModifier.ensureKindExists(insert, newAccountType, Phone.CONTENT_ITEM_TYPE);
-        EntityModifier.ensureKindExists(insert, newAccountType, Email.CONTENT_ITEM_TYPE);
-        EntityModifier.ensureKindExists(insert, newAccountType, Organization.CONTENT_ITEM_TYPE);
-        EntityModifier.ensureKindExists(insert, newAccountType, Event.CONTENT_ITEM_TYPE);
-        EntityModifier.ensureKindExists(insert, newAccountType, StructuredPostal.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(insert, newAccountType, Phone.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(insert, newAccountType, Email.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(insert, newAccountType, Organization.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(insert, newAccountType, Event.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(insert, newAccountType,
+                StructuredPostal.CONTENT_ITEM_TYPE);
 
         // Set the correct URI for saving the contact as a profile
         if (mNewLocalProfile) {
@@ -666,7 +663,7 @@
 
         if (mState == null) {
             // Create state if none exists yet
-            mState = EntityDeltaList.fromSingle(insert);
+            mState = RawContactDeltaList.fromSingle(insert);
         } else {
             // Add contact onto end of existing state
             mState.add(insert);
@@ -690,14 +687,11 @@
         int numRawContacts = mState.size();
         for (int i = 0; i < numRawContacts; i++) {
             // TODO ensure proper ordering of entities in the list
-            final EntityDelta entity = mState.get(i);
-            final ValuesDelta values = entity.getValues();
-            if (!values.isVisible()) continue;
+            final RawContactDelta rawContactDelta = mState.get(i);
+            if (!rawContactDelta.isVisible()) continue;
 
-            final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = values.getAsString(RawContacts.DATA_SET);
-            final AccountType type = accountTypes.getAccountType(accountType, dataSet);
-            final long rawContactId = values.getAsLong(RawContacts._ID);
+            final AccountType type = rawContactDelta.getAccountType(accountTypes);
+            final long rawContactId = rawContactDelta.getRawContactId();
 
             final BaseRawContactEditorView editor;
             if (!type.areContactsWritable()) {
@@ -724,7 +718,7 @@
 
             mContent.addView(editor);
 
-            editor.setState(entity, type, mViewIdGenerator, isEditingUserProfile());
+            editor.setState(rawContactDelta, type, mViewIdGenerator, isEditingUserProfile());
 
             // Set up the photo handler.
             bindPhotoHandler(editor, type, mState);
@@ -797,7 +791,7 @@
     }
 
     private void bindPhotoHandler(BaseRawContactEditorView editor, AccountType type,
-            EntityDeltaList state) {
+            RawContactDeltaList state) {
         final int mode;
         if (type.areContactsWritable()) {
             if (editor.hasSetPhoto()) {
@@ -852,11 +846,10 @@
         // Find the associated account for this contact (retrieve it here because there are
         // multiple paths to creating a contact and this ensures we always have the correct
         // account).
-        final EntityDelta entity = mState.get(0);
-        final ValuesDelta values = entity.getValues();
-        String name = values.getAsString(RawContacts.ACCOUNT_NAME);
-        String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
-        String dataSet = values.getAsString(RawContacts.DATA_SET);
+        final RawContactDelta rawContactDelta = mState.get(0);
+        String name = rawContactDelta.getAccountName();
+        String type = rawContactDelta.getAccountType();
+        String dataSet = rawContactDelta.getDataSet();
 
         AccountWithDataSet account = (name == null || type == null) ? null :
                 new AccountWithDataSet(name, type, dataSet);
@@ -864,12 +857,11 @@
     }
 
     private void addAccountSwitcher(
-            final EntityDelta currentState, BaseRawContactEditorView editor) {
-        ValuesDelta values = currentState.getValues();
+            final RawContactDelta currentState, BaseRawContactEditorView editor) {
         final AccountWithDataSet currentAccount = new AccountWithDataSet(
-                values.getAsString(RawContacts.ACCOUNT_NAME),
-                values.getAsString(RawContacts.ACCOUNT_TYPE),
-                values.getAsString(RawContacts.DATA_SET));
+                currentState.getAccountName(),
+                currentState.getAccountType(),
+                currentState.getDataSet());
         final View accountView = editor.findViewById(R.id.account);
         final View anchorView = editor.findViewById(R.id.account_container);
         accountView.setOnClickListener(new View.OnClickListener() {
@@ -1005,7 +997,7 @@
      */
     private boolean hasPendingChanges() {
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
-        return EntityModifier.hasChanges(mState, accountTypes);
+        return RawContactModifier.hasChanges(mState, accountTypes);
     }
 
     /**
@@ -1209,10 +1201,8 @@
         final AccountTypeManager accountTypes = AccountTypeManager.getInstance(mContext);
         int size = mState.size();
         for (int i = 0; i < size; i++) {
-            ValuesDelta values = mState.get(i).getValues();
-            final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = values.getAsString(RawContacts.DATA_SET);
-            final AccountType type = accountTypes.getAccountType(accountType, dataSet);
+            RawContactDelta entity = mState.get(i);
+            final AccountType type = entity.getAccountType(accountTypes);
             if (type.areContactsWritable()) {
                 return true;
             }
@@ -1273,12 +1263,12 @@
                 Bundle intentExtras, boolean redirect);
     }
 
-    private class EntityDeltaComparator implements Comparator<EntityDelta> {
+    private class EntityDeltaComparator implements Comparator<RawContactDelta> {
         /**
          * Compare EntityDeltas for sorting the stack of editors.
          */
         @Override
-        public int compare(EntityDelta one, EntityDelta two) {
+        public int compare(RawContactDelta one, RawContactDelta two) {
             // Check direct equality
             if (one.equals(two)) {
                 return 0;
@@ -1333,11 +1323,9 @@
             }
 
             // Check account name
-            ValuesDelta oneValues = one.getValues();
-            String oneAccount = oneValues.getAsString(RawContacts.ACCOUNT_NAME);
+            String oneAccount = one.getAccountName();
             if (oneAccount == null) oneAccount = "";
-            ValuesDelta twoValues = two.getValues();
-            String twoAccount = twoValues.getAsString(RawContacts.ACCOUNT_NAME);
+            String twoAccount = two.getAccountName();
             if (twoAccount == null) twoAccount = "";
             value = oneAccount.compareTo(twoAccount);
             if (value != 0) {
@@ -1345,8 +1333,8 @@
             }
 
             // Both are in the same account, fall back to contact ID
-            Long oneId = oneValues.getAsLong(RawContacts._ID);
-            Long twoId = twoValues.getAsLong(RawContacts._ID);
+            Long oneId = one.getRawContactId();
+            Long twoId = two.getRawContactId();
             if (oneId == null) {
                 return -1;
             } else if (twoId == null) {
@@ -1362,7 +1350,7 @@
      */
     protected long getContactId() {
         if (mState != null) {
-            for (EntityDelta rawContact : mState) {
+            for (RawContactDelta rawContact : mState) {
                 Long contactId = rawContact.getValues().getAsLong(RawContacts.CONTACT_ID);
                 if (contactId != null) {
                     return contactId;
@@ -1662,14 +1650,13 @@
         int countWithPicture = 0;
         final int numEntities = mState.size();
         for (int i = 0; i < numEntities; i++) {
-            final EntityDelta entity = mState.get(i);
-            final ValuesDelta values = entity.getValues();
-            if (values.isVisible()) {
+            final RawContactDelta entity = mState.get(i);
+            if (entity.isVisible()) {
                 final ValuesDelta primary = entity.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
-                if (primary != null && primary.getAsByteArray(Photo.PHOTO) != null) {
+                if (primary != null && primary.getPhoto() != null) {
                     countWithPicture++;
                 } else {
-                    final long rawContactId = values.getAsLong(RawContacts._ID);
+                    final long rawContactId = entity.getRawContactId();
                     final String path = mUpdatedPhotos.getString(String.valueOf(rawContactId));
                     if (path != null) {
                         final File file = new File(path);
@@ -1690,16 +1677,16 @@
     /**
      * The listener for the data loader
      */
-    private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDataLoaderListener =
-            new LoaderCallbacks<ContactLoader.Result>() {
+    private final LoaderManager.LoaderCallbacks<Contact> mDataLoaderListener =
+            new LoaderCallbacks<Contact>() {
         @Override
-        public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+        public Loader<Contact> onCreateLoader(int id, Bundle args) {
             mLoaderStartTime = SystemClock.elapsedRealtime();
             return new ContactLoader(mContext, mLookupUri, true);
         }
 
         @Override
-        public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
+        public void onLoadFinished(Loader<Contact> loader, Contact data) {
             final long loaderCurrentTime = SystemClock.elapsedRealtime();
             Log.v(TAG, "Time needed for loading: " + (loaderCurrentTime-mLoaderStartTime));
             if (!data.isLoaded()) {
@@ -1719,7 +1706,7 @@
         }
 
         @Override
-        public void onLoaderReset(Loader<ContactLoader.Result> loader) {
+        public void onLoaderReset(Loader<Contact> loader) {
         }
     };
 
@@ -1772,7 +1759,7 @@
         private final PhotoActionListener mPhotoEditorListener;
 
         public PhotoHandler(Context context, BaseRawContactEditorView editor, int photoMode,
-                EntityDeltaList state) {
+                RawContactDeltaList state) {
             super(context, editor.getPhotoEditor(), photoMode, false, state);
             mEditor = editor;
             mRawContactId = editor.getRawContactId();
diff --git a/src/com/android/contacts/editor/ContactEditorUtils.java b/src/com/android/contacts/editor/ContactEditorUtils.java
index 0029b14..2791c2d 100644
--- a/src/com/android/contacts/editor/ContactEditorUtils.java
+++ b/src/com/android/contacts/editor/ContactEditorUtils.java
@@ -26,9 +26,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.test.NeededForTesting;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
diff --git a/src/com/android/contacts/editor/Editor.java b/src/com/android/contacts/editor/Editor.java
index 025f092..05336f2 100644
--- a/src/com/android/contacts/editor/Editor.java
+++ b/src/com/android/contacts/editor/Editor.java
@@ -18,9 +18,9 @@
 
 import android.provider.ContactsContract.Data;
 
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataKind;
 
 /**
  * Generic definition of something that edits a {@link Data} row through an
@@ -60,7 +60,7 @@
      * builds any needed views. Any changes performed by the user will be
      * written back to that same object.
      */
-    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta values, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig);
 
     public void setDeletable(boolean deletable);
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index ff2622f..ca1bf64 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -29,11 +29,11 @@
 import com.android.contacts.datepicker.DatePicker;
 import com.android.contacts.datepicker.DatePickerDialog;
 import com.android.contacts.datepicker.DatePickerDialog.OnDateSetListener;
-import com.android.contacts.model.AccountType.EditField;
-import com.android.contacts.model.AccountType.EventEditType;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.account.AccountType.EditField;
+import com.android.contacts.model.account.AccountType.EventEditType;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.DateUtils;
 
 import java.text.ParsePosition;
@@ -108,7 +108,7 @@
     }
 
     @Override
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         if (kind.fieldList.size() != 1) throw new IllegalStateException("kind must have 1 field");
         super.setValues(kind, entry, state, readOnly, vig);
diff --git a/src/com/android/contacts/editor/GroupMembershipView.java b/src/com/android/contacts/editor/GroupMembershipView.java
index f405e4d..b84e22b 100644
--- a/src/com/android/contacts/editor/GroupMembershipView.java
+++ b/src/com/android/contacts/editor/GroupMembershipView.java
@@ -21,7 +21,6 @@
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
@@ -40,10 +39,10 @@
 import com.android.contacts.R;
 import com.android.contacts.interactions.GroupCreationDialogFragment;
 import com.android.contacts.interactions.GroupCreationDialogFragment.OnGroupCreatedListener;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.internal.util.Objects;
 
 import java.util.ArrayList;
@@ -127,7 +126,7 @@
         }
     }
 
-    private EntityDelta mState;
+    private RawContactDelta mState;
     private Cursor mGroupMetaData;
     private String mAccountName;
     private String mAccountType;
@@ -197,12 +196,11 @@
         }
     }
 
-    public void setState(EntityDelta state) {
+    public void setState(RawContactDelta state) {
         mState = state;
-        ValuesDelta values = state.getValues();
-        mAccountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-        mAccountName = values.getAsString(RawContacts.ACCOUNT_NAME);
-        mDataSet = values.getAsString(RawContacts.DATA_SET);
+        mAccountType = mState.getAccountType();
+        mAccountName = mState.getAccountName();
+        mDataSet = mState.getDataSet();
         mDefaultGroupVisibilityKnown = false;
         mCreatedNewGroup = false;
         updateView();
@@ -357,7 +355,7 @@
         if (entries != null) {
             for (ValuesDelta entry : entries) {
                 if (!entry.isDelete()) {
-                    Long groupId = entry.getAsLong(GroupMembership.GROUP_ROW_ID);
+                    Long groupId = entry.getGroupRowId();
                     if (groupId != null && groupId != mFavoritesGroupId
                             && (groupId != mDefaultGroupId || mDefaultGroupVisible)
                             && !isGroupChecked(groupId)) {
@@ -372,8 +370,8 @@
             GroupSelectionItem item = mAdapter.getItem(i);
             long groupId = item.getGroupId();
             if (item.isChecked() && !hasMembership(groupId)) {
-                ValuesDelta entry = EntityModifier.insertChild(mState, mKind);
-                entry.put(GroupMembership.GROUP_ROW_ID, groupId);
+                ValuesDelta entry = RawContactModifier.insertChild(mState, mKind);
+                entry.setGroupRowId(groupId);
             }
         }
 
@@ -400,7 +398,7 @@
         if (entries != null) {
             for (ValuesDelta values : entries) {
                 if (!values.isDelete()) {
-                    Long id = values.getAsLong(GroupMembership.GROUP_ROW_ID);
+                    Long id = values.getGroupRowId();
                     if (id != null && id == groupId) {
                         return true;
                     }
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index ae0262a..2d4263b 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -28,10 +28,10 @@
 
 import com.android.contacts.R;
 import com.android.contacts.editor.Editor.EditorListener;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataKind;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -50,7 +50,7 @@
     private String mTitleString;
 
     private DataKind mKind;
-    private EntityDelta mState;
+    private RawContactDelta mState;
     private boolean mReadOnly;
 
     private ViewIdGenerator mViewIdGenerator;
@@ -136,7 +136,7 @@
         }
     }
 
-    public void setState(DataKind kind, EntityDelta state, boolean readOnly, ViewIdGenerator vig) {
+    public void setState(DataKind kind, RawContactDelta state, boolean readOnly, ViewIdGenerator vig) {
         mKind = kind;
         mState = state;
         mReadOnly = readOnly;
@@ -237,7 +237,7 @@
             updateEmptyEditors();
             // If there are no existing empty editors and it's possible to add
             // another field, then make the "add footer" field visible.
-            if (!hasEmptyEditor() && EntityModifier.canInsert(mState, mKind)) {
+            if (!hasEmptyEditor() && RawContactModifier.canInsert(mState, mKind)) {
                 if (animate) {
                     EditorAnimator.getInstance().showAddFieldFooter(mAddFieldFooter);
                 } else {
@@ -364,7 +364,7 @@
 
         // Insert a new child, create its view and set its focus
         if (values == null) {
-            values = EntityModifier.insertChild(mState, mKind);
+            values = RawContactModifier.insertChild(mState, mKind);
         }
 
         final View newField = createEditorView(values);
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index 288e915..789f425 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnShowListener;
-import android.content.Entity;
 import android.os.Bundle;
 import android.os.Handler;
 import android.text.Editable;
@@ -47,11 +46,11 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.DialogManager;
 import com.android.contacts.util.DialogManager.DialogShowingView;
 
@@ -59,7 +58,7 @@
 
 /**
  * Base class for editors that handles labels and values. Uses
- * {@link ValuesDelta} to read any existing {@link Entity} values, and to
+ * {@link ValuesDelta} to read any existing {@link RawContact} values, and to
  * correctly write any changes values.
  */
 public abstract class LabeledEditorView extends LinearLayout implements Editor, DialogShowingView {
@@ -76,7 +75,7 @@
 
     private DataKind mKind;
     private ValuesDelta mEntry;
-    private EntityDelta mState;
+    private RawContactDelta mState;
     private boolean mReadOnly;
     private boolean mWasEmpty = true;
     private boolean mIsDeletable = true;
@@ -342,7 +341,7 @@
      * structure and {@link ValuesDelta} describing the content to edit.
      */
     @Override
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         mKind = kind;
         mEntry = entry;
@@ -359,11 +358,11 @@
         setVisibility(View.VISIBLE);
 
         // Display label selector if multiple types available
-        final boolean hasTypes = EntityModifier.hasEditTypes(kind);
+        final boolean hasTypes = RawContactModifier.hasEditTypes(kind);
         setupLabelButton(hasTypes);
         mLabel.setEnabled(!readOnly && isEnabled());
         if (hasTypes) {
-            mType = EntityModifier.getCurrentType(entry, kind);
+            mType = RawContactModifier.getCurrentType(entry, kind);
             rebuildLabel();
         }
     }
@@ -398,7 +397,7 @@
                 final String customText = editText.getText().toString().trim();
                 if (ContactsUtils.isGraphic(customText)) {
                     final List<EditType> allTypes =
-                            EntityModifier.getValidTypes(mState, mKind, null);
+                            RawContactModifier.getValidTypes(mState, mKind, null);
                     mType = null;
                     for (EditType editType : allTypes) {
                         if (editType.customColumn != null) {
@@ -534,7 +533,7 @@
                 }
             }
 
-            addAll(EntityModifier.getValidTypes(mState, mKind, mType));
+            addAll(RawContactModifier.getValidTypes(mState, mKind, mType));
         }
 
         public boolean hasCustomSelection() {
diff --git a/src/com/android/contacts/editor/PhoneticNameEditorView.java b/src/com/android/contacts/editor/PhoneticNameEditorView.java
index 1700817..8cbb921 100644
--- a/src/com/android/contacts/editor/PhoneticNameEditorView.java
+++ b/src/com/android/contacts/editor/PhoneticNameEditorView.java
@@ -16,15 +16,15 @@
 
 package com.android.contacts.editor;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.dataitem.StructuredNameDataItem;
 
 /**
  * A dedicated editor for phonetic name. It is similar to {@link StructuredNameEditorView}.
@@ -61,19 +61,16 @@
         }
 
         private void parsePhoneticName(String value) {
-            ContentValues values = PhoneticNameEditorView.parsePhoneticName(value, null);
-            mValues.put(StructuredName.PHONETIC_FAMILY_NAME,
-                    values.getAsString(StructuredName.PHONETIC_FAMILY_NAME));
-            mValues.put(StructuredName.PHONETIC_MIDDLE_NAME,
-                    values.getAsString(StructuredName.PHONETIC_MIDDLE_NAME));
-            mValues.put(StructuredName.PHONETIC_GIVEN_NAME,
-                    values.getAsString(StructuredName.PHONETIC_GIVEN_NAME));
+            StructuredNameDataItem dataItem = PhoneticNameEditorView.parsePhoneticName(value, null);
+            mValues.setPhoneticFamilyName(dataItem.getPhoneticFamilyName());
+            mValues.setPhoneticMiddleName(dataItem.getPhoneticMiddleName());
+            mValues.setPhoneticGivenName(dataItem.getPhoneticGivenName());
         }
 
         private void buildPhoneticName() {
-            String family = mValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
-            String middle = mValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
-            String given = mValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+            String family = mValues.getPhoneticFamilyName();
+            String middle = mValues.getPhoneticMiddleName();
+            String given = mValues.getPhoneticGivenName();
             mPhoneticName = PhoneticNameEditorView.buildPhoneticName(family, middle, given);
         }
 
@@ -100,7 +97,8 @@
      * created.
      * @return ContentValues with parsed data. Those data can be null.
      */
-    public static ContentValues parsePhoneticName(String phoneticName, ContentValues values) {
+    public static StructuredNameDataItem parsePhoneticName(String phoneticName,
+            StructuredNameDataItem item) {
         String family = null;
         String middle = null;
         String given = null;
@@ -123,13 +121,13 @@
             }
         }
 
-        if (values == null) {
-            values = new ContentValues();
+        if (item == null) {
+            item = new StructuredNameDataItem();
         }
-        values.put(StructuredName.PHONETIC_FAMILY_NAME, family);
-        values.put(StructuredName.PHONETIC_MIDDLE_NAME, middle);
-        values.put(StructuredName.PHONETIC_GIVEN_NAME, given);
-        return values;
+        item.setPhoneticFamilyName(family);
+        item.setPhoneticMiddleName(middle);
+        item.setPhoneticGivenName(given);
+        return item;
     }
 
     /**
@@ -172,7 +170,7 @@
     }
 
     @Override
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         if (!(entry instanceof PhoneticValuesDelta)) {
             entry = new PhoneticValuesDelta(entry);
@@ -213,9 +211,9 @@
     public boolean hasData() {
         ValuesDelta entry = getEntry();
 
-        String family = entry.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
-        String middle = entry.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
-        String given = entry.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+        String family = entry.getPhoneticFamilyName();
+        String middle = entry.getPhoneticMiddleName();
+        String given = entry.getPhoneticGivenName();
 
         return !TextUtils.isEmpty(family) || !TextUtils.isEmpty(middle)
                 || !TextUtils.isEmpty(given);
diff --git a/src/com/android/contacts/editor/PhotoEditorView.java b/src/com/android/contacts/editor/PhotoEditorView.java
index 71a4197..30c3bb4 100644
--- a/src/com/android/contacts/editor/PhotoEditorView.java
+++ b/src/com/android/contacts/editor/PhotoEditorView.java
@@ -27,9 +27,9 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.ContactPhotoUtils;
 
 /**
@@ -92,7 +92,7 @@
 
     /** {@inheritDoc} */
     @Override
-    public void setValues(DataKind kind, ValuesDelta values, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta values, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         mEntry = values;
         mReadOnly = readOnly;
@@ -143,7 +143,7 @@
         mEntry.setFromTemplate(false);
 
         // When the user chooses a new photo mark it as super primary
-        mEntry.put(Photo.IS_SUPER_PRIMARY, 1);
+        mEntry.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
@@ -154,7 +154,7 @@
         final int size = ContactsUtils.getThumbnailSize(getContext());
         final Bitmap scaled = Bitmap.createScaledBitmap(photo, size, size, false);
         final byte[] compressed = ContactPhotoUtils.compressBitmap(scaled);
-        if (compressed != null) mEntry.put(Photo.PHOTO, compressed);
+        if (compressed != null) mEntry.setPhoto(compressed);
     }
 
     /**
diff --git a/src/com/android/contacts/editor/RawContactEditorView.java b/src/com/android/contacts/editor/RawContactEditorView.java
index 51ba400..daccdd8 100644
--- a/src/com/android/contacts/editor/RawContactEditorView.java
+++ b/src/com/android/contacts/editor/RawContactEditorView.java
@@ -17,7 +17,6 @@
 package com.android.contacts.editor;
 
 import android.content.Context;
-import android.content.Entity;
 import android.database.Cursor;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
@@ -25,7 +24,6 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -40,26 +38,26 @@
 
 import com.android.contacts.GroupMetaDataLoader;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.internal.util.Objects;
 
 import java.util.ArrayList;
 
 /**
  * Custom view that provides all the editor interaction for a specific
- * {@link Contacts} represented through an {@link EntityDelta}. Callers can
+ * {@link Contacts} represented through an {@link RawContactDelta}. Callers can
  * reuse this view and quickly rebuild its contents through
- * {@link #setState(EntityDelta, AccountType, ViewIdGenerator)}.
+ * {@link #setState(RawContactDelta, AccountType, ViewIdGenerator)}.
  * <p>
  * Internal updates are performed against {@link ValuesDelta} so that the
- * source {@link Entity} can be swapped out. Any state-based changes, such as
+ * source {@link RawContact} can be swapped out. Any state-based changes, such as
  * adding {@link Data} rows or changing {@link EditType}, are performed through
- * {@link EntityModifier} to ensure that {@link AccountType} are enforced.
+ * {@link RawContactModifier} to ensure that {@link AccountType} are enforced.
  */
 public class RawContactEditorView extends BaseRawContactEditorView {
     private LayoutInflater mInflater;
@@ -80,7 +78,7 @@
     private boolean mAutoAddToDefaultGroup = true;
     private Cursor mGroupMetaData;
     private DataKind mGroupMembershipKind;
-    private EntityDelta mState;
+    private RawContactDelta mState;
 
     private boolean mPhoneticNameAdded;
 
@@ -152,11 +150,11 @@
 
     /**
      * Set the internal state for this view, given a current
-     * {@link EntityDelta} state and the {@link AccountType} that
+     * {@link RawContactDelta} state and the {@link AccountType} that
      * apply to that state.
      */
     @Override
-    public void setState(EntityDelta state, AccountType type, ViewIdGenerator vig,
+    public void setState(RawContactDelta state, AccountType type, ViewIdGenerator vig,
             boolean isProfile) {
 
         mState = state;
@@ -170,15 +168,14 @@
         setId(vig.getId(state, null, null, ViewIdGenerator.NO_VIEW_INDEX));
 
         // Make sure we have a StructuredName and Organization
-        EntityModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
-        EntityModifier.ensureKindExists(state, type, Organization.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(state, type, Organization.CONTENT_ITEM_TYPE);
 
-        ValuesDelta values = state.getValues();
-        mRawContactId = values.getAsLong(RawContacts._ID);
+        mRawContactId = state.getRawContactId();
 
         // Fill in the account info
         if (isProfile) {
-            String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
+            String accountName = state.getAccountName();
             if (TextUtils.isEmpty(accountName)) {
                 mAccountNameTextView.setVisibility(View.GONE);
                 mAccountTypeTextView.setText(R.string.local_profile_title);
@@ -189,7 +186,7 @@
                 mAccountNameTextView.setText(accountName);
             }
         } else {
-            String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
+            String accountName = state.getAccountName();
             CharSequence accountType = type.getDisplayLabel(mContext);
             if (TextUtils.isEmpty(accountType)) {
                 accountType = mContext.getString(R.string.account_phone);
@@ -208,7 +205,7 @@
         mAccountIcon.setImageDrawable(type.getDisplayIcon(mContext));
 
         // Show photo editor when supported
-        EntityModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
         setHasPhotoEditor((type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null));
         getPhotoEditor().setEnabled(isEnabled());
         mName.setEnabled(isEnabled());
@@ -341,7 +338,7 @@
         ArrayList<ValuesDelta> entries = mState.getMimeEntries(GroupMembership.CONTENT_ITEM_TYPE);
         if (entries != null) {
             for (ValuesDelta values : entries) {
-                Long id = values.getAsLong(GroupMembership.GROUP_ROW_ID);
+                Long id = values.getGroupRowId();
                 if (id != null && id.longValue() != 0) {
                     hasGroupMembership = true;
                     break;
@@ -352,8 +349,8 @@
         if (!hasGroupMembership) {
             long defaultGroupId = getDefaultGroupId();
             if (defaultGroupId != -1) {
-                ValuesDelta entry = EntityModifier.insertChild(mState, mGroupMembershipKind);
-                entry.put(GroupMembership.GROUP_ROW_ID, defaultGroupId);
+                ValuesDelta entry = RawContactModifier.insertChild(mState, mGroupMembershipKind);
+                entry.setGroupRowId(defaultGroupId);
             }
         }
     }
@@ -363,9 +360,9 @@
      * account.  Returns -1 if there is no such group.
      */
     private long getDefaultGroupId() {
-        String accountType = mState.getValues().getAsString(RawContacts.ACCOUNT_TYPE);
-        String accountName = mState.getValues().getAsString(RawContacts.ACCOUNT_NAME);
-        String accountDataSet = mState.getValues().getAsString(RawContacts.DATA_SET);
+        String accountType = mState.getAccountType();
+        String accountName = mState.getAccountName();
+        String accountDataSet = mState.getDataSet();
         mGroupMetaData.moveToPosition(-1);
         while (mGroupMetaData.moveToNext()) {
             String name = mGroupMetaData.getString(GroupMetaDataLoader.ACCOUNT_NAME);
diff --git a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
index 25edca9..8e51d18 100644
--- a/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
+++ b/src/com/android/contacts/editor/RawContactReadOnlyEditorView.java
@@ -39,12 +39,12 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityModifier;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.dataitem.DataKind;
 
 import java.util.ArrayList;
 
@@ -108,11 +108,11 @@
 
     /**
      * Set the internal state for this view, given a current
-     * {@link EntityDelta} state and the {@link AccountType} that
+     * {@link RawContactDelta} state and the {@link AccountType} that
      * apply to that state.
      */
     @Override
-    public void setState(EntityDelta state, AccountType type, ViewIdGenerator vig,
+    public void setState(RawContactDelta state, AccountType type, ViewIdGenerator vig,
             boolean isProfile) {
         // Remove any existing sections
         mGeneral.removeAllViews();
@@ -121,13 +121,12 @@
         if (state == null || type == null) return;
 
         // Make sure we have StructuredName
-        EntityModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(state, type, StructuredName.CONTENT_ITEM_TYPE);
 
         // Fill in the header info
-        ValuesDelta values = state.getValues();
-        mAccountName = values.getAsString(RawContacts.ACCOUNT_NAME);
-        mAccountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-        mDataSet = values.getAsString(RawContacts.DATA_SET);
+        mAccountName = state.getAccountName();
+        mAccountType = state.getAccountType();
+        mDataSet = state.getDataSet();
 
         if (isProfile) {
             if (TextUtils.isEmpty(mAccountName)) {
@@ -162,14 +161,14 @@
 
         mAccountIcon.setImageDrawable(type.getDisplayIcon(mContext));
 
-        mRawContactId = values.getAsLong(RawContacts._ID);
+        mRawContactId = state.getRawContactId();
 
         ValuesDelta primary;
 
         // Photo
         DataKind kind = type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE);
         if (kind != null) {
-            EntityModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
+            RawContactModifier.ensureKindExists(state, type, Photo.CONTENT_ITEM_TYPE);
             boolean hasPhotoEditor = type.getKindForMimetype(Photo.CONTENT_ITEM_TYPE) != null;
             setHasPhotoEditor(hasPhotoEditor);
             primary = state.getPrimaryEntry(Photo.CONTENT_ITEM_TYPE);
@@ -203,13 +202,13 @@
             for (int i = 0; i < phones.size(); i++) {
                 ValuesDelta phone = phones.get(i);
                 final String phoneNumber = PhoneNumberUtils.formatNumber(
-                        phone.getAsString(Phone.NUMBER),
-                        phone.getAsString(Phone.NORMALIZED_NUMBER),
+                        phone.getPhoneNumber(),
+                        phone.getPhoneNormalizedNumber(),
                         ContactsUtils.getCurrentCountryIso(getContext()));
                 final CharSequence phoneType;
-                if (phone.containsKey(Phone.TYPE)) {
+                if (phone.phoneHasType()) {
                     phoneType = Phone.getTypeLabel(
-                            res, phone.getAsInteger(Phone.TYPE), phone.getAsString(Phone.LABEL));
+                            res, phone.getPhoneType(), phone.getPhoneLabel());
                 } else {
                     phoneType = null;
                 }
@@ -223,11 +222,11 @@
         if (emails != null) {
             for (int i = 0; i < emails.size(); i++) {
                 ValuesDelta email = emails.get(i);
-                final String emailAddress = email.getAsString(Email.DATA);
+                final String emailAddress = email.getEmailData();
                 final CharSequence emailType;
-                if (email.containsKey(Email.TYPE)) {
+                if (email.emailHasType()) {
                     emailType = Email.getTypeLabel(
-                            res, email.getAsInteger(Email.TYPE), email.getAsString(Email.LABEL));
+                            res, email.getEmailType(), email.getEmailLabel());
                 } else {
                     emailType = null;
                 }
diff --git a/src/com/android/contacts/editor/SelectAccountDialogFragment.java b/src/com/android/contacts/editor/SelectAccountDialogFragment.java
index b9b9f7b..3e7ad0e 100644
--- a/src/com/android/contacts/editor/SelectAccountDialogFragment.java
+++ b/src/com/android/contacts/editor/SelectAccountDialogFragment.java
@@ -24,7 +24,7 @@
 import android.content.DialogInterface;
 import android.os.Bundle;
 
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.AccountsListAdapter;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 
diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java
index ac25f22..3c4476d 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -25,9 +25,11 @@
 import android.text.TextUtils;
 import android.util.AttributeSet;
 
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.dataitem.StructuredNameDataItem;
 import com.android.contacts.util.NameConverter;
 
 import java.util.HashMap;
@@ -45,7 +47,7 @@
  */
 public class StructuredNameEditorView extends TextFieldsEditorView {
 
-    private ContentValues mSnapshot;
+    private StructuredNameDataItem mSnapshot;
     private boolean mChanged;
 
     public StructuredNameEditorView(Context context) {
@@ -61,11 +63,12 @@
     }
 
     @Override
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         super.setValues(kind, entry, state, readOnly, vig);
         if (mSnapshot == null) {
-            mSnapshot = new ContentValues(getValues().getCompleteValues());
+            mSnapshot = (StructuredNameDataItem) DataItem.createFrom(null,
+                    new ContentValues(getValues().getCompleteValues()));
             mChanged = entry.isInsert();
         } else {
             mChanged = false;
@@ -114,12 +117,12 @@
 
         if (!mChanged) {
             for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
-                values.put(field, mSnapshot.getAsString(field));
+                values.put(field, mSnapshot.getContentValues().getAsString(field));
             }
             return;
         }
 
-        String displayName = values.getAsString(StructuredName.DISPLAY_NAME);
+        String displayName = values.getDisplayName();
         Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName(
                 getContext(), displayName);
         if (!structuredNameMap.isEmpty()) {
@@ -129,17 +132,16 @@
             }
         }
 
-        mSnapshot.clear();
-        mSnapshot.putAll(values.getCompleteValues());
-        mSnapshot.put(StructuredName.DISPLAY_NAME, displayName);
+        mSnapshot.getContentValues().clear();
+        mSnapshot.getContentValues().putAll(values.getCompleteValues());
+        mSnapshot.setDisplayName(displayName);
     }
 
     private void switchFromStructuredNameToFullName() {
         ValuesDelta values = getValues();
 
         if (!mChanged) {
-            values.put(StructuredName.DISPLAY_NAME,
-                    mSnapshot.getAsString(StructuredName.DISPLAY_NAME));
+            values.setDisplayName(mSnapshot.getDisplayName());
             return;
         }
 
@@ -151,10 +153,10 @@
             values.put(StructuredName.DISPLAY_NAME, displayName);
         }
 
-        mSnapshot.clear();
-        mSnapshot.put(StructuredName.DISPLAY_NAME, values.getAsString(StructuredName.DISPLAY_NAME));
+        mSnapshot.getContentValues().clear();
+        mSnapshot.setDisplayName(values.getDisplayName());
         for (String field : structuredNameMap.keySet()) {
-            mSnapshot.put(field, structuredNameMap.get(field));
+            mSnapshot.getContentValues().put(field, structuredNameMap.get(field));
         }
     }
 
@@ -167,14 +169,14 @@
     }
 
     private void eraseFullName(ValuesDelta values) {
-        values.putNull(StructuredName.DISPLAY_NAME);
+        values.setDisplayName(null);
     }
 
     private void rebuildFullName(ValuesDelta values) {
         Map<String, String> structuredNameMap = valuesToStructuredNameMap(values);
         String displayName = NameConverter.structuredNameToDisplayName(getContext(),
                 structuredNameMap);
-        values.put(StructuredName.DISPLAY_NAME, displayName);
+        values.setDisplayName(displayName);
     }
 
     private void eraseStructuredName(ValuesDelta values) {
@@ -184,7 +186,7 @@
     }
 
     private void rebuildStructuredName(ValuesDelta values) {
-        String displayName = values.getAsString(StructuredName.DISPLAY_NAME);
+        String displayName = values.getDisplayName();
         Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName(
                 getContext(), displayName);
         for (String field : structuredNameMap.keySet()) {
@@ -202,7 +204,7 @@
     protected Parcelable onSaveInstanceState() {
         SavedState state = new SavedState(super.onSaveInstanceState());
         state.mChanged = mChanged;
-        state.mSnapshot = mSnapshot;
+        state.mSnapshot = mSnapshot.getContentValues();
         return state;
     }
 
@@ -212,7 +214,7 @@
         super.onRestoreInstanceState(ss.mSuperState);
 
         mChanged = ss.mChanged;
-        mSnapshot = ss.mSnapshot;
+        mSnapshot = (StructuredNameDataItem) DataItem.createFrom(null, ss.mSnapshot);
     }
 
     private static class SavedState implements Parcelable {
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index b558a4b..8558b11 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -17,7 +17,6 @@
 package com.android.contacts.editor;
 
 import android.content.Context;
-import android.content.Entity;
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -38,15 +37,15 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType.EditField;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.account.AccountType.EditField;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.PhoneNumberFormatter;
 
 /**
  * Simple editor that handles labels and any {@link EditField} defined for the
- * entry. Uses {@link ValuesDelta} to read any existing {@link Entity} values,
+ * entry. Uses {@link ValuesDelta} to read any existing {@link RawContact} values,
  * and to correctly write any changes values.
  */
 public class TextFieldsEditorView extends LabeledEditorView {
@@ -171,7 +170,7 @@
     }
 
     @Override
-    public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
+    public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
             ViewIdGenerator vig) {
         super.setValues(kind, entry, state, readOnly, vig);
         // Remove edit texts that we currently have
diff --git a/src/com/android/contacts/editor/ViewIdGenerator.java b/src/com/android/contacts/editor/ViewIdGenerator.java
index 4ab9279..55a42a7 100644
--- a/src/com/android/contacts/editor/ViewIdGenerator.java
+++ b/src/com/android/contacts/editor/ViewIdGenerator.java
@@ -20,9 +20,9 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.dataitem.DataKind;
 
 /**
  * A class that provides unique view ids for {@link ContentEditorView}, {@link KindSectionView},
@@ -62,13 +62,13 @@
     /**
      * Returns an id for a view associated with specified contact field.
      *
-     * @param entity {@link EntityDelta} associated with the view
+     * @param entity {@link RawContactDelta} associated with the view
      * @param kind {@link DataKind} associated with the view, or null if none exists.
      * @param values {@link ValuesDelta} associated with the view, or null if none exists.
      * @param viewIndex index of the view in the parent {@link Editor}, if it's a leave view.
      *     Otherwise, pass {@link #NO_VIEW_INDEX}.
      */
-    public int getId(EntityDelta entity, DataKind kind, ValuesDelta values,
+    public int getId(RawContactDelta entity, DataKind kind, ValuesDelta values,
             int viewIndex) {
         final String k = getMapKey(entity, kind, values, viewIndex);
 
@@ -81,7 +81,7 @@
         return id;
     }
 
-    private static String getMapKey(EntityDelta entity, DataKind kind, ValuesDelta values,
+    private static String getMapKey(RawContactDelta entity, DataKind kind, ValuesDelta values,
             int viewIndex) {
         sWorkStringBuilder.setLength(0);
         if (entity != null) {
diff --git a/src/com/android/contacts/group/GroupBrowseListAdapter.java b/src/com/android/contacts/group/GroupBrowseListAdapter.java
index ae58f73..acea625 100644
--- a/src/com/android/contacts/group/GroupBrowseListAdapter.java
+++ b/src/com/android/contacts/group/GroupBrowseListAdapter.java
@@ -29,8 +29,8 @@
 
 import com.android.contacts.GroupListLoader;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 import com.android.internal.util.Objects;
 
 /**
diff --git a/src/com/android/contacts/group/GroupDetailDisplayUtils.java b/src/com/android/contacts/group/GroupDetailDisplayUtils.java
index 4865196..d4e43a0 100644
--- a/src/com/android/contacts/group/GroupDetailDisplayUtils.java
+++ b/src/com/android/contacts/group/GroupDetailDisplayUtils.java
@@ -23,8 +23,8 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 
 public class GroupDetailDisplayUtils {
 
diff --git a/src/com/android/contacts/group/GroupDetailFragment.java b/src/com/android/contacts/group/GroupDetailFragment.java
index 5f07c64..6294b40 100644
--- a/src/com/android/contacts/group/GroupDetailFragment.java
+++ b/src/com/android/contacts/group/GroupDetailFragment.java
@@ -53,8 +53,8 @@
 import com.android.contacts.list.ContactTileAdapter;
 import com.android.contacts.list.ContactTileAdapter.DisplayType;
 import com.android.contacts.list.ContactTileView;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 
 /**
  * Displays the details of a group and shows a list of actions possible for the group.
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 5f9beb3..762867c 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -66,9 +66,9 @@
 import com.android.contacts.activities.GroupEditorActivity;
 import com.android.contacts.editor.SelectAccountDialogFragment;
 import com.android.contacts.group.SuggestedMemberListAdapter.SuggestedMember;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.util.ViewUtil;
 import com.android.internal.util.Objects;
diff --git a/src/com/android/contacts/interactions/ContactDeletionInteraction.java b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
index ead7010..ea6e6dc 100644
--- a/src/com/android/contacts/interactions/ContactDeletionInteraction.java
+++ b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
@@ -35,8 +35,8 @@
 
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Sets;
 
diff --git a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
index 9eb4de9..5731bc1 100644
--- a/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
+++ b/src/com/android/contacts/interactions/GroupCreationDialogFragment.java
@@ -23,7 +23,7 @@
 
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 
 /**
  * A dialog for creating a new group.
diff --git a/src/com/android/contacts/interactions/ImportExportDialogFragment.java b/src/com/android/contacts/interactions/ImportExportDialogFragment.java
index 94c0616..f63f84c 100644
--- a/src/com/android/contacts/interactions/ImportExportDialogFragment.java
+++ b/src/com/android/contacts/interactions/ImportExportDialogFragment.java
@@ -40,7 +40,7 @@
 import com.android.contacts.R;
 import com.android.contacts.editor.SelectAccountDialogFragment;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.AccountSelectionUtil;
 import com.android.contacts.util.AccountsListAdapter.AccountListFilter;
 import com.android.contacts.vcard.ExportVCardActivity;
diff --git a/src/com/android/contacts/interactions/PhoneNumberInteraction.java b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
index 22cbd7d..207ceea 100644
--- a/src/com/android/contacts/interactions/PhoneNumberInteraction.java
+++ b/src/com/android/contacts/interactions/PhoneNumberInteraction.java
@@ -53,10 +53,10 @@
 import com.android.contacts.R;
 import com.android.contacts.activities.DialtactsActivity;
 import com.android.contacts.activities.TransactionSafeActivity;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountType.StringInflater;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountType.StringInflater;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.DataKind;
 import com.google.common.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/list/AccountFilterActivity.java b/src/com/android/contacts/list/AccountFilterActivity.java
index b9e5b03..c5a21ba 100644
--- a/src/com/android/contacts/list/AccountFilterActivity.java
+++ b/src/com/android/contacts/list/AccountFilterActivity.java
@@ -36,9 +36,9 @@
 
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
diff --git a/src/com/android/contacts/list/ContactListFilterController.java b/src/com/android/contacts/list/ContactListFilterController.java
index aec670f..ddcb1ae 100644
--- a/src/com/android/contacts/list/ContactListFilterController.java
+++ b/src/com/android/contacts/list/ContactListFilterController.java
@@ -20,7 +20,7 @@
 import android.preference.PreferenceManager;
 
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/contacts/list/ContactListFilterView.java b/src/com/android/contacts/list/ContactListFilterView.java
index 7763846..d0ecfe4 100644
--- a/src/com/android/contacts/list/ContactListFilterView.java
+++ b/src/com/android/contacts/list/ContactListFilterView.java
@@ -26,8 +26,8 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.account.AccountType;
 
 /**
  * Contact list filter parameters.
diff --git a/src/com/android/contacts/list/CustomContactListFilterActivity.java b/src/com/android/contacts/list/CustomContactListFilterActivity.java
index a113f0c..8842a1d 100644
--- a/src/com/android/contacts/list/CustomContactListFilterActivity.java
+++ b/src/com/android/contacts/list/CustomContactListFilterActivity.java
@@ -27,7 +27,6 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.EntityIterator;
 import android.content.Intent;
 import android.content.Loader;
 import android.content.OperationApplicationException;
@@ -56,11 +55,11 @@
 
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.GoogleAccountType;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.GoogleAccountType;
 import com.android.contacts.util.EmptyService;
 import com.android.contacts.util.LocalizedNameResolver;
 import com.android.contacts.util.WeakAsyncTask;
@@ -146,7 +145,8 @@
                 if (account.dataSet != null) {
                     groupsUri.appendQueryParameter(Groups.DATA_SET, account.dataSet).build();
                 }
-                EntityIterator iterator = ContactsContract.Groups.newEntityIterator(resolver.query(
+                android.content.EntityIterator iterator =
+                        ContactsContract.Groups.newEntityIterator(resolver.query(
                         groupsUri.build(), null, null, null, null));
                 try {
                     boolean hasGroups = false;
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 7c56e15..64f3a91 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -45,6 +45,14 @@
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.list.ContactListFilterController;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountTypeWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.ExchangeAccountType;
+import com.android.contacts.model.account.ExternalAccountType;
+import com.android.contacts.model.account.FallbackAccountType;
+import com.android.contacts.model.account.GoogleAccountType;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.Constants;
 import com.android.internal.util.Objects;
 import com.google.common.annotations.VisibleForTesting;
diff --git a/src/com/android/contacts/model/Contact.java b/src/com/android/contacts/model/Contact.java
new file mode 100644
index 0000000..ede2101
--- /dev/null
+++ b/src/com/android/contacts/model/Contact.java
@@ -0,0 +1,495 @@
+/*
+ * 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.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.DisplayNameSources;
+
+import com.android.contacts.GroupMetaData;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.util.DataStatus;
+import com.android.contacts.util.StreamItemEntry;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.ArrayList;
+
+/**
+ * A Contact represents a single person or logical entity as perceived by the user.  The information
+ * about a contact can come from multiple data sources, which are each represented by a RawContact
+ * object.  Thus, a Contact is associated with a collection of RawContact objects.
+ *
+ * The aggregation of raw contacts into a single contact is performed automatically, and it is
+ * also possible for users to manually split and join raw contacts into various contacts.
+ *
+ * Only the {@link ContactLoader} class can create a Contact object with various flags to allow
+ * partial loading of contact data.  Thus, an instance of this class should be treated as
+ * a read-only object.
+ */
+public class Contact {
+    private enum Status {
+        /** Contact is successfully loaded */
+        LOADED,
+        /** There was an error loading the contact */
+        ERROR,
+        /** Contact is not found */
+        NOT_FOUND,
+    }
+
+    private final Uri mRequestedUri;
+    private final Uri mLookupUri;
+    private final Uri mUri;
+    private final long mDirectoryId;
+    private final String mLookupKey;
+    private final long mId;
+    private final long mNameRawContactId;
+    private final int mDisplayNameSource;
+    private final long mPhotoId;
+    private final String mPhotoUri;
+    private final String mDisplayName;
+    private final String mAltDisplayName;
+    private final String mPhoneticName;
+    private final boolean mStarred;
+    private final Integer mPresence;
+    private ImmutableList<RawContact> mRawContacts;
+    private ImmutableList<StreamItemEntry> mStreamItems;
+    private ImmutableMap<Long,DataStatus> mStatuses;
+    private ImmutableList<AccountType> mInvitableAccountTypes;
+
+    private String mDirectoryDisplayName;
+    private String mDirectoryType;
+    private String mDirectoryAccountType;
+    private String mDirectoryAccountName;
+    private int mDirectoryExportSupport;
+
+    private ImmutableList<GroupMetaData> mGroups;
+
+    private byte[] mPhotoBinaryData;
+    private final boolean mSendToVoicemail;
+    private final String mCustomRingtone;
+    private final boolean mIsUserProfile;
+
+    private final Contact.Status mStatus;
+    private final Exception mException;
+
+    /**
+     * Constructor for special results, namely "no contact found" and "error".
+     */
+    private Contact(Uri requestedUri, Contact.Status status, Exception exception) {
+        if (status == Status.ERROR && exception == null) {
+            throw new IllegalArgumentException("ERROR result must have exception");
+        }
+        mStatus = status;
+        mException = exception;
+        mRequestedUri = requestedUri;
+        mLookupUri = null;
+        mUri = null;
+        mDirectoryId = -1;
+        mLookupKey = null;
+        mId = -1;
+        mRawContacts = null;
+        mStreamItems = null;
+        mStatuses = null;
+        mNameRawContactId = -1;
+        mDisplayNameSource = DisplayNameSources.UNDEFINED;
+        mPhotoId = -1;
+        mPhotoUri = null;
+        mDisplayName = null;
+        mAltDisplayName = null;
+        mPhoneticName = null;
+        mStarred = false;
+        mPresence = null;
+        mInvitableAccountTypes = null;
+        mSendToVoicemail = false;
+        mCustomRingtone = null;
+        mIsUserProfile = false;
+    }
+
+    public static Contact forError(Uri requestedUri, Exception exception) {
+        return new Contact(requestedUri, Status.ERROR, exception);
+    }
+
+    public static Contact forNotFound(Uri requestedUri) {
+        return new Contact(requestedUri, Status.NOT_FOUND, null);
+    }
+
+    /**
+     * Constructor to call when contact was found
+     */
+    public Contact(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
+            long id, long nameRawContactId, int displayNameSource, long photoId,
+            String photoUri, String displayName, String altDisplayName, String phoneticName,
+            boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
+            boolean isUserProfile) {
+        mStatus = Status.LOADED;
+        mException = null;
+        mRequestedUri = requestedUri;
+        mLookupUri = lookupUri;
+        mUri = uri;
+        mDirectoryId = directoryId;
+        mLookupKey = lookupKey;
+        mId = id;
+        mRawContacts = null;
+        mStreamItems = null;
+        mStatuses = null;
+        mNameRawContactId = nameRawContactId;
+        mDisplayNameSource = displayNameSource;
+        mPhotoId = photoId;
+        mPhotoUri = photoUri;
+        mDisplayName = displayName;
+        mAltDisplayName = altDisplayName;
+        mPhoneticName = phoneticName;
+        mStarred = starred;
+        mPresence = presence;
+        mInvitableAccountTypes = null;
+        mSendToVoicemail = sendToVoicemail;
+        mCustomRingtone = customRingtone;
+        mIsUserProfile = isUserProfile;
+    }
+
+    public Contact(Uri requestedUri, Contact from) {
+        mRequestedUri = requestedUri;
+
+        mStatus = from.mStatus;
+        mException = from.mException;
+        mLookupUri = from.mLookupUri;
+        mUri = from.mUri;
+        mDirectoryId = from.mDirectoryId;
+        mLookupKey = from.mLookupKey;
+        mId = from.mId;
+        mNameRawContactId = from.mNameRawContactId;
+        mDisplayNameSource = from.mDisplayNameSource;
+        mPhotoId = from.mPhotoId;
+        mPhotoUri = from.mPhotoUri;
+        mDisplayName = from.mDisplayName;
+        mAltDisplayName = from.mAltDisplayName;
+        mPhoneticName = from.mPhoneticName;
+        mStarred = from.mStarred;
+        mPresence = from.mPresence;
+        mRawContacts = from.mRawContacts;
+        mStreamItems = from.mStreamItems;
+        mStatuses = from.mStatuses;
+        mInvitableAccountTypes = from.mInvitableAccountTypes;
+
+        mDirectoryDisplayName = from.mDirectoryDisplayName;
+        mDirectoryType = from.mDirectoryType;
+        mDirectoryAccountType = from.mDirectoryAccountType;
+        mDirectoryAccountName = from.mDirectoryAccountName;
+        mDirectoryExportSupport = from.mDirectoryExportSupport;
+
+        mGroups = from.mGroups;
+
+        mPhotoBinaryData = from.mPhotoBinaryData;
+        mSendToVoicemail = from.mSendToVoicemail;
+        mCustomRingtone = from.mCustomRingtone;
+        mIsUserProfile = from.mIsUserProfile;
+    }
+
+    /**
+     * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
+     */
+    public void setDirectoryMetaData(String displayName, String directoryType,
+            String accountType, String accountName, int exportSupport) {
+        mDirectoryDisplayName = displayName;
+        mDirectoryType = directoryType;
+        mDirectoryAccountType = accountType;
+        mDirectoryAccountName = accountName;
+        mDirectoryExportSupport = exportSupport;
+    }
+
+    /* package */ void setPhotoBinaryData(byte[] photoBinaryData) {
+        mPhotoBinaryData = photoBinaryData;
+    }
+
+    /**
+     * Returns the URI for the contact that contains both the lookup key and the ID. This is
+     * the best URI to reference a contact.
+     * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
+     */
+    public Uri getLookupUri() {
+        return mLookupUri;
+    }
+
+    public String getLookupKey() {
+        return mLookupKey;
+    }
+
+    /**
+     * Returns the contact Uri that was passed to the provider to make the query. This is
+     * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
+     * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
+     * always reference the full aggregate contact.
+     */
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
+     * Returns the URI for which this {@link ContactLoader) was initially requested.
+     */
+    public Uri getRequestedUri() {
+        return mRequestedUri;
+    }
+
+    /**
+     * Instantiate a new RawContactDeltaList for this contact.
+     */
+    public RawContactDeltaList createRawContactDeltaList() {
+        return RawContactDeltaList.fromIterator(getRawContacts().iterator());
+    }
+
+    /**
+     * Returns the contact ID.
+     */
+    @VisibleForTesting
+    /* package */ long getId() {
+        return mId;
+    }
+
+    /**
+     * @return true when an exception happened during loading, in which case
+     *     {@link #getException} returns the actual exception object.
+     *     Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
+     *     {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
+     *     and vice versa.
+     */
+    public boolean isError() {
+        return mStatus == Status.ERROR;
+    }
+
+    public Exception getException() {
+        return mException;
+    }
+
+    /**
+     * @return true when the specified contact is not found.
+     *     Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
+     *     {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
+     *     and vice versa.
+     */
+    public boolean isNotFound() {
+        return mStatus == Status.NOT_FOUND;
+    }
+
+    /**
+     * @return true if the specified contact is successfully loaded.
+     *     i.e. neither {@link #isError()} nor {@link #isNotFound()}.
+     */
+    public boolean isLoaded() {
+        return mStatus == Status.LOADED;
+    }
+
+    public long getNameRawContactId() {
+        return mNameRawContactId;
+    }
+
+    public int getDisplayNameSource() {
+        return mDisplayNameSource;
+    }
+
+    public long getPhotoId() {
+        return mPhotoId;
+    }
+
+    public String getPhotoUri() {
+        return mPhotoUri;
+    }
+
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    public String getAltDisplayName() {
+        return mAltDisplayName;
+    }
+
+    public String getPhoneticName() {
+        return mPhoneticName;
+    }
+
+    public boolean getStarred() {
+        return mStarred;
+    }
+
+    public Integer getPresence() {
+        return mPresence;
+    }
+
+    /**
+     * This can return non-null invitable account types only if the {@link ContactLoader} was
+     * configured to load invitable account types in its constructor.
+     * @return
+     */
+    public ImmutableList<AccountType> getInvitableAccountTypes() {
+        return mInvitableAccountTypes;
+    }
+
+    public ImmutableList<RawContact> getRawContacts() {
+        return mRawContacts;
+    }
+
+    /**
+     * This can return non-null stream items only if the {@link ContactLoader} was
+     * configured to load stream items in its constructor.
+     * @return
+     */
+    public ImmutableList<StreamItemEntry> getStreamItems() {
+        return mStreamItems;
+    }
+
+    public ImmutableMap<Long, DataStatus> getStatuses() {
+        return mStatuses;
+    }
+
+    public long getDirectoryId() {
+        return mDirectoryId;
+    }
+
+    public boolean isDirectoryEntry() {
+        return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
+                && mDirectoryId != Directory.LOCAL_INVISIBLE;
+    }
+
+    /**
+     * @return true if this is a contact (not group, etc.) with at least one
+     *         writable raw-contact, and false otherwise.
+     */
+    public boolean isWritableContact(final Context context) {
+        return getFirstWritableRawContactId(context) != -1;
+    }
+
+    /**
+     * Return the ID of the first raw-contact in the contact data that belongs to a
+     * contact-writable account, or -1 if no such entity exists.
+     */
+    public long getFirstWritableRawContactId(final Context context) {
+        // Directory entries are non-writable
+        if (isDirectoryEntry()) return -1;
+
+        // Iterate through raw-contacts; if we find a writable on, return its ID.
+        for (RawContact rawContact : getRawContacts()) {
+            AccountType accountType = rawContact.getAccountType();
+            if (accountType != null && accountType.areContactsWritable()) {
+                return rawContact.getId();
+            }
+        }
+        // No writable raw-contact was found.
+        return -1;
+    }
+
+    public int getDirectoryExportSupport() {
+        return mDirectoryExportSupport;
+    }
+
+    public String getDirectoryDisplayName() {
+        return mDirectoryDisplayName;
+    }
+
+    public String getDirectoryType() {
+        return mDirectoryType;
+    }
+
+    public String getDirectoryAccountType() {
+        return mDirectoryAccountType;
+    }
+
+    public String getDirectoryAccountName() {
+        return mDirectoryAccountName;
+    }
+
+    public byte[] getPhotoBinaryData() {
+        return mPhotoBinaryData;
+    }
+
+    public ArrayList<ContentValues> getContentValues() {
+        if (mRawContacts.size() != 1) {
+            throw new IllegalStateException(
+                    "Cannot extract content values from an aggregated contact");
+        }
+
+        RawContact rawContact = mRawContacts.get(0);
+        ArrayList<ContentValues> result = new ArrayList<ContentValues>();
+        for (DataItem dataItem : rawContact.getDataItems()) {
+            result.add(dataItem.getContentValues());
+        }
+
+        // If the photo was loaded using the URI, create an entry for the photo
+        // binary data.
+        if (mPhotoId == 0 && mPhotoBinaryData != null) {
+            ContentValues photo = new ContentValues();
+            photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+            photo.put(Photo.PHOTO, mPhotoBinaryData);
+            result.add(photo);
+        }
+
+        return result;
+    }
+
+    /**
+     * This can return non-null group meta-data only if the {@link ContactLoader} was configured to
+     * load group metadata in its constructor.
+     * @return
+     */
+    public ImmutableList<GroupMetaData> getGroupMetaData() {
+        return mGroups;
+    }
+
+    public boolean isSendToVoicemail() {
+        return mSendToVoicemail;
+    }
+
+    public String getCustomRingtone() {
+        return mCustomRingtone;
+    }
+
+    public boolean isUserProfile() {
+        return mIsUserProfile;
+    }
+
+    @Override
+    public String toString() {
+        return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
+                ",uri=" + mUri + ",status=" + mStatus + "}";
+    }
+
+    /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) {
+        mRawContacts = rawContacts;
+    }
+
+    /* package */ void setStatuses(ImmutableMap<Long, DataStatus> statuses) {
+        mStatuses = statuses;
+    }
+
+    /* package */ void setInvitableAccountTypes(ImmutableList<AccountType> accountTypes) {
+        mInvitableAccountTypes = accountTypes;
+    }
+
+    /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
+        mGroups = groups;
+    }
+
+    /* package */ void setStreamItems(ImmutableList<StreamItemEntry> streamItems) {
+        mStreamItems = streamItems;
+    }
+}
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/model/ContactLoader.java
similarity index 63%
rename from src/com/android/contacts/ContactLoader.java
rename to src/com/android/contacts/model/ContactLoader.java
index c0aacae..6db997e 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/model/ContactLoader.java
@@ -14,15 +14,13 @@
  * limitations under the License
  */
 
-package com.android.contacts;
+package com.android.contacts.model;
 
 import android.content.AsyncTaskLoader;
 import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Entity;
-import android.content.Entity.NamedContentValues;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -32,11 +30,9 @@
 import android.net.Uri;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.DisplayNameSources;
 import android.provider.ContactsContract.Groups;
 import android.provider.ContactsContract.RawContacts;
 import android.provider.ContactsContract.StreamItemPhotos;
@@ -45,17 +41,18 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountTypeWithDataSet;
-import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.GroupMetaData;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountTypeWithDataSet;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.PhotoDataItem;
 import com.android.contacts.util.ContactLoaderUtils;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.android.contacts.util.UriUtils;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Lists;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 
@@ -64,20 +61,19 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 /**
  * Loads a single Contact and all it constituent RawContacts.
  */
-public class ContactLoader extends AsyncTaskLoader<ContactLoader.Result> {
+public class ContactLoader extends AsyncTaskLoader<Contact> {
     private static final String TAG = ContactLoader.class.getSimpleName();
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     /** A short-lived cache that can be set by {@link #cacheResult()} */
-    private static Result sCachedResult = null;
+    private static Contact sCachedResult = null;
 
     private final Uri mRequestedUri;
     private Uri mLookupUri;
@@ -85,7 +81,7 @@
     private boolean mLoadStreamItems;
     private boolean mLoadInvitableAccountTypes;
     private boolean mPostViewNotification;
-    private Result mContact;
+    private Contact mContact;
     private ForceLoadContentObserver mObserver;
     private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet();
 
@@ -106,433 +102,6 @@
     }
 
     /**
-     * The result of a load operation. Contains all data necessary to display the contact.
-     */
-    public static final class Result {
-        private enum Status {
-            /** Contact is successfully loaded */
-            LOADED,
-            /** There was an error loading the contact */
-            ERROR,
-            /** Contact is not found */
-            NOT_FOUND,
-        }
-
-        private final Uri mRequestedUri;
-        private final Uri mLookupUri;
-        private final Uri mUri;
-        private final long mDirectoryId;
-        private final String mLookupKey;
-        private final long mId;
-        private final long mNameRawContactId;
-        private final int mDisplayNameSource;
-        private final long mPhotoId;
-        private final String mPhotoUri;
-        private final String mDisplayName;
-        private final String mAltDisplayName;
-        private final String mPhoneticName;
-        private final boolean mStarred;
-        private final Integer mPresence;
-        private final ArrayList<Entity> mEntities;
-        private ArrayList<StreamItemEntry> mStreamItems;
-        private final LongSparseArray<DataStatus> mStatuses;
-        private ArrayList<AccountType> mInvitableAccountTypes;
-
-        private String mDirectoryDisplayName;
-        private String mDirectoryType;
-        private String mDirectoryAccountType;
-        private String mDirectoryAccountName;
-        private int mDirectoryExportSupport;
-
-        private ArrayList<GroupMetaData> mGroups;
-
-        private byte[] mPhotoBinaryData;
-        private final boolean mSendToVoicemail;
-        private final String mCustomRingtone;
-        private final boolean mIsUserProfile;
-
-        private final Status mStatus;
-        private final Exception mException;
-
-        /**
-         * Constructor for special results, namely "no contact found" and "error".
-         */
-        private Result(Uri requestedUri, Status status, Exception exception) {
-            if (status == Status.ERROR && exception == null) {
-                throw new IllegalArgumentException("ERROR result must have exception");
-            }
-            mStatus = status;
-            mException = exception;
-            mRequestedUri = requestedUri;
-            mLookupUri = null;
-            mUri = null;
-            mDirectoryId = -1;
-            mLookupKey = null;
-            mId = -1;
-            mEntities = null;
-            mStreamItems = null;
-            mStatuses = null;
-            mNameRawContactId = -1;
-            mDisplayNameSource = DisplayNameSources.UNDEFINED;
-            mPhotoId = -1;
-            mPhotoUri = null;
-            mDisplayName = null;
-            mAltDisplayName = null;
-            mPhoneticName = null;
-            mStarred = false;
-            mPresence = null;
-            mInvitableAccountTypes = null;
-            mSendToVoicemail = false;
-            mCustomRingtone = null;
-            mIsUserProfile = false;
-        }
-
-        private static Result forError(Uri requestedUri, Exception exception) {
-            return new Result(requestedUri, Status.ERROR, exception);
-        }
-
-        private static Result forNotFound(Uri requestedUri) {
-            return new Result(requestedUri, Status.NOT_FOUND, null);
-        }
-
-        /**
-         * Constructor to call when contact was found
-         */
-        private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
-                long id, long nameRawContactId, int displayNameSource, long photoId,
-                String photoUri, String displayName, String altDisplayName, String phoneticName,
-                boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
-                boolean isUserProfile) {
-            mStatus = Status.LOADED;
-            mException = null;
-            mRequestedUri = requestedUri;
-            mLookupUri = lookupUri;
-            mUri = uri;
-            mDirectoryId = directoryId;
-            mLookupKey = lookupKey;
-            mId = id;
-            mEntities = new ArrayList<Entity>();
-            mStreamItems = null;
-            mStatuses = new LongSparseArray<DataStatus>();
-            mNameRawContactId = nameRawContactId;
-            mDisplayNameSource = displayNameSource;
-            mPhotoId = photoId;
-            mPhotoUri = photoUri;
-            mDisplayName = displayName;
-            mAltDisplayName = altDisplayName;
-            mPhoneticName = phoneticName;
-            mStarred = starred;
-            mPresence = presence;
-            mInvitableAccountTypes = null;
-            mSendToVoicemail = sendToVoicemail;
-            mCustomRingtone = customRingtone;
-            mIsUserProfile = isUserProfile;
-        }
-
-        private Result(Uri requestedUri, Result from) {
-            mRequestedUri = requestedUri;
-
-            mStatus = from.mStatus;
-            mException = from.mException;
-            mLookupUri = from.mLookupUri;
-            mUri = from.mUri;
-            mDirectoryId = from.mDirectoryId;
-            mLookupKey = from.mLookupKey;
-            mId = from.mId;
-            mNameRawContactId = from.mNameRawContactId;
-            mDisplayNameSource = from.mDisplayNameSource;
-            mPhotoId = from.mPhotoId;
-            mPhotoUri = from.mPhotoUri;
-            mDisplayName = from.mDisplayName;
-            mAltDisplayName = from.mAltDisplayName;
-            mPhoneticName = from.mPhoneticName;
-            mStarred = from.mStarred;
-            mPresence = from.mPresence;
-            mEntities = from.mEntities;
-            mStreamItems = from.mStreamItems;
-            mStatuses = from.mStatuses;
-            mInvitableAccountTypes = from.mInvitableAccountTypes;
-
-            mDirectoryDisplayName = from.mDirectoryDisplayName;
-            mDirectoryType = from.mDirectoryType;
-            mDirectoryAccountType = from.mDirectoryAccountType;
-            mDirectoryAccountName = from.mDirectoryAccountName;
-            mDirectoryExportSupport = from.mDirectoryExportSupport;
-
-            mGroups = from.mGroups;
-
-            mPhotoBinaryData = from.mPhotoBinaryData;
-            mSendToVoicemail = from.mSendToVoicemail;
-            mCustomRingtone = from.mCustomRingtone;
-            mIsUserProfile = from.mIsUserProfile;
-        }
-
-        /**
-         * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
-         */
-        private void setDirectoryMetaData(String displayName, String directoryType,
-                String accountType, String accountName, int exportSupport) {
-            mDirectoryDisplayName = displayName;
-            mDirectoryType = directoryType;
-            mDirectoryAccountType = accountType;
-            mDirectoryAccountName = accountName;
-            mDirectoryExportSupport = exportSupport;
-        }
-
-        private void setPhotoBinaryData(byte[] photoBinaryData) {
-            mPhotoBinaryData = photoBinaryData;
-        }
-
-        /**
-         * Returns the URI for the contact that contains both the lookup key and the ID. This is
-         * the best URI to reference a contact.
-         * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
-         */
-        public Uri getLookupUri() {
-            return mLookupUri;
-        }
-
-        public String getLookupKey() {
-            return mLookupKey;
-        }
-
-        /**
-         * Returns the contact Uri that was passed to the provider to make the query. This is
-         * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
-         * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
-         * always reference the full aggregate contact.
-         */
-        public Uri getUri() {
-            return mUri;
-        }
-
-        /**
-         * Returns the URI for which this {@link ContactLoader) was initially requested.
-         */
-        public Uri getRequestedUri() {
-            return mRequestedUri;
-        }
-
-        /**
-         * Instantiate a new EntityDeltaList for this contact.
-         */
-        public EntityDeltaList createEntityDeltaList() {
-            return EntityDeltaList.fromIterator(getEntities().iterator());
-        }
-
-        /**
-         * Returns the contact ID.
-         */
-        @VisibleForTesting
-        /* package */ long getId() {
-            return mId;
-        }
-
-        /**
-         * @return true when an exception happened during loading, in which case
-         *     {@link #getException} returns the actual exception object.
-         *     Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
-         *     {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
-         *     and vice versa.
-         */
-        public boolean isError() {
-            return mStatus == Status.ERROR;
-        }
-
-        public Exception getException() {
-            return mException;
-        }
-
-        /**
-         * @return true when the specified contact is not found.
-         *     Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
-         *     {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
-         *     and vice versa.
-         */
-        public boolean isNotFound() {
-            return mStatus == Status.NOT_FOUND;
-        }
-
-        /**
-         * @return true if the specified contact is successfully loaded.
-         *     i.e. neither {@link #isError()} nor {@link #isNotFound()}.
-         */
-        public boolean isLoaded() {
-            return mStatus == Status.LOADED;
-        }
-
-        public long getNameRawContactId() {
-            return mNameRawContactId;
-        }
-
-        public int getDisplayNameSource() {
-            return mDisplayNameSource;
-        }
-
-        public long getPhotoId() {
-            return mPhotoId;
-        }
-
-        public String getPhotoUri() {
-            return mPhotoUri;
-        }
-
-        public String getDisplayName() {
-            return mDisplayName;
-        }
-
-        public String getAltDisplayName() {
-            return mAltDisplayName;
-        }
-
-        public String getPhoneticName() {
-            return mPhoneticName;
-        }
-
-        public boolean getStarred() {
-            return mStarred;
-        }
-
-        public Integer getPresence() {
-            return mPresence;
-        }
-
-        public ArrayList<AccountType> getInvitableAccountTypes() {
-            return mInvitableAccountTypes;
-        }
-
-        public ArrayList<Entity> getEntities() {
-            return mEntities;
-        }
-
-        public ArrayList<StreamItemEntry> getStreamItems() {
-            return mStreamItems;
-        }
-
-        public LongSparseArray<DataStatus> getStatuses() {
-            return mStatuses;
-        }
-
-        public long getDirectoryId() {
-            return mDirectoryId;
-        }
-
-        public boolean isDirectoryEntry() {
-            return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
-                    && mDirectoryId != Directory.LOCAL_INVISIBLE;
-        }
-
-        /**
-         * @return true if this is a contact (not group, etc.) with at least one
-         *         writable raw-contact, and false otherwise.
-         */
-        public boolean isWritableContact(final Context context) {
-            return getFirstWritableRawContactId(context) != -1;
-        }
-
-        /**
-         * Return the ID of the first raw-contact in the contact data that belongs to a
-         * contact-writable account, or -1 if no such entity exists.
-         */
-        public long getFirstWritableRawContactId(final Context context) {
-            // Directory entries are non-writable
-            if (isDirectoryEntry()) return -1;
-
-            // Iterate through raw-contacts; if we find a writable on, return its ID.
-            final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
-            for (Entity entity : getEntities()) {
-                ContentValues values = entity.getEntityValues();
-                String type = values.getAsString(RawContacts.ACCOUNT_TYPE);
-                String dataSet = values.getAsString(RawContacts.DATA_SET);
-
-                AccountType accountType = accountTypes.getAccountType(type, dataSet);
-                if (accountType != null && accountType.areContactsWritable()) {
-                    return values.getAsLong(RawContacts._ID);
-                }
-            }
-            // No writable raw-contact was found.
-            return -1;
-        }
-
-        public int getDirectoryExportSupport() {
-            return mDirectoryExportSupport;
-        }
-
-        public String getDirectoryDisplayName() {
-            return mDirectoryDisplayName;
-        }
-
-        public String getDirectoryType() {
-            return mDirectoryType;
-        }
-
-        public String getDirectoryAccountType() {
-            return mDirectoryAccountType;
-        }
-
-        public String getDirectoryAccountName() {
-            return mDirectoryAccountName;
-        }
-
-        public byte[] getPhotoBinaryData() {
-            return mPhotoBinaryData;
-        }
-
-        public ArrayList<ContentValues> getContentValues() {
-            if (mEntities.size() != 1) {
-                throw new IllegalStateException(
-                        "Cannot extract content values from an aggregated contact");
-            }
-
-            Entity entity = mEntities.get(0);
-            ArrayList<ContentValues> result = new ArrayList<ContentValues>();
-            ArrayList<NamedContentValues> subValues = entity.getSubValues();
-            if (subValues != null) {
-                int size = subValues.size();
-                for (int i = 0; i < size; i++) {
-                    NamedContentValues pair = subValues.get(i);
-                    if (Data.CONTENT_URI.equals(pair.uri)) {
-                        result.add(pair.values);
-                    }
-                }
-            }
-
-            // If the photo was loaded using the URI, create an entry for the photo
-            // binary data.
-            if (mPhotoId == 0 && mPhotoBinaryData != null) {
-                ContentValues photo = new ContentValues();
-                photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
-                photo.put(Photo.PHOTO, mPhotoBinaryData);
-                result.add(photo);
-            }
-
-            return result;
-        }
-
-        public List<GroupMetaData> getGroupMetaData() {
-            return mGroups;
-        }
-
-        public boolean isSendToVoicemail() {
-            return mSendToVoicemail;
-        }
-
-        public String getCustomRingtone() {
-            return mCustomRingtone;
-        }
-
-        public boolean isUserProfile() {
-            return mIsUserProfile;
-        }
-
-        @Override
-        public String toString() {
-            return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
-                    ",uri=" + mUri + ",status=" + mStatus + "}";
-        }
-    }
-
-    /**
      * Projection used for the query that loads all data for the entire contact (except for
      * social stream items).
      */
@@ -726,21 +295,21 @@
     }
 
     @Override
-    public Result loadInBackground() {
+    public Contact loadInBackground() {
         try {
             final ContentResolver resolver = getContext().getContentResolver();
             final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri(
                     resolver, mLookupUri);
-            final Result cachedResult = sCachedResult;
+            final Contact cachedResult = sCachedResult;
             sCachedResult = null;
             // Is this the same Uri as what we had before already? In that case, reuse that result
-            final Result result;
+            final Contact result;
             final boolean resultIsCached;
             if (cachedResult != null &&
                     UriUtils.areEqual(cachedResult.getLookupUri(), mLookupUri)) {
                 // We are using a cached result from earlier. Below, we should make sure
                 // we are not doing any more network or disc accesses
-                result = new Result(mRequestedUri, cachedResult);
+                result = new Contact(mRequestedUri, cachedResult);
                 resultIsCached = true;
             } else {
                 result = loadContactEntity(resolver, uriCurrentFormat);
@@ -769,57 +338,62 @@
             return result;
         } catch (Exception e) {
             Log.e(TAG, "Error loading the contact: " + mLookupUri, e);
-            return Result.forError(mRequestedUri, e);
+            return Contact.forError(mRequestedUri, e);
         }
     }
 
-    private Result loadContactEntity(ContentResolver resolver, Uri contactUri) {
+    private Contact loadContactEntity(ContentResolver resolver, Uri contactUri) {
         Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY);
         Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null,
                 Contacts.Entity.RAW_CONTACT_ID);
         if (cursor == null) {
             Log.e(TAG, "No cursor returned in loadContactEntity");
-            return Result.forNotFound(mRequestedUri);
+            return Contact.forNotFound(mRequestedUri);
         }
 
         try {
             if (!cursor.moveToFirst()) {
                 cursor.close();
-                return Result.forNotFound(mRequestedUri);
+                return Contact.forNotFound(mRequestedUri);
             }
 
-            // Create the loaded result starting with the Contact data.
-            Result result = loadContactHeaderData(cursor, contactUri);
+            // Create the loaded contact starting with the header data.
+            Contact contact = loadContactHeaderData(cursor, contactUri);
 
             // Fill in the raw contacts, which is wrapped in an Entity and any
             // status data.  Initially, result has empty entities and statuses.
             long currentRawContactId = -1;
-            Entity entity = null;
-            ArrayList<Entity> entities = result.getEntities();
-            LongSparseArray<DataStatus> statuses = result.getStatuses();
-            for (; !cursor.isAfterLast(); cursor.moveToNext()) {
+            RawContact rawContact = null;
+            ImmutableList.Builder<RawContact> rawContactsBuilder =
+                    new ImmutableList.Builder<RawContact>();
+            ImmutableMap.Builder<Long, DataStatus> statusesBuilder =
+                    new ImmutableMap.Builder<Long, DataStatus>();
+            do {
                 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID);
                 if (rawContactId != currentRawContactId) {
                     // First time to see this raw contact id, so create a new entity, and
                     // add it to the result's entities.
                     currentRawContactId = rawContactId;
-                    entity = new android.content.Entity(loadRawContact(cursor));
-                    entities.add(entity);
+                    rawContact = new RawContact(getContext(), loadRawContactValues(cursor));
+                    rawContactsBuilder.add(rawContact);
                 }
                 if (!cursor.isNull(ContactQuery.DATA_ID)) {
-                    ContentValues data = loadData(cursor);
-                    entity.addSubValue(ContactsContract.Data.CONTENT_URI, data);
+                    ContentValues data = loadDataValues(cursor);
+                    rawContact.addDataItemValues(data);
 
                     if (!cursor.isNull(ContactQuery.PRESENCE)
                             || !cursor.isNull(ContactQuery.STATUS)) {
                         final DataStatus status = new DataStatus(cursor);
                         final long dataId = cursor.getLong(ContactQuery.DATA_ID);
-                        statuses.put(dataId, status);
+                        statusesBuilder.put(dataId, status);
                     }
                 }
-            }
+            } while (cursor.moveToNext());
 
-            return result;
+            contact.setRawContacts(rawContactsBuilder.build());
+            contact.setStatuses(statusesBuilder.build());
+
+            return contact;
         } finally {
             cursor.close();
         }
@@ -829,7 +403,7 @@
      * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If
      * not found, returns null
      */
-    private void loadPhotoBinaryData(Result contactData) {
+    private void loadPhotoBinaryData(Contact contactData) {
 
         // If we have a photo URI, try loading that first.
         String photoUri = contactData.getPhotoUri();
@@ -863,17 +437,15 @@
             return;
         }
 
-        for (Entity entity : contactData.getEntities()) {
-            for (NamedContentValues subValue : entity.getSubValues()) {
-                final ContentValues entryValues = subValue.values;
-                final long dataId = entryValues.getAsLong(Data._ID);
-                if (dataId == photoId) {
-                    final String mimeType = entryValues.getAsString(Data.MIMETYPE);
-                    // Correct Data Id but incorrect MimeType? Don't load
-                    if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                        return;
+        for (RawContact rawContact : contactData.getRawContacts()) {
+            for (DataItem dataItem : rawContact.getDataItems()) {
+                if (dataItem.getId() == photoId) {
+                    if (!(dataItem instanceof PhotoDataItem)) {
+                        break;
                     }
-                    contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO));
+
+                    final PhotoDataItem photo = (PhotoDataItem) dataItem;
+                    contactData.setPhotoBinaryData(photo.getPhoto());
                     break;
                 }
             }
@@ -881,10 +453,11 @@
     }
 
     /**
-     * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}.
+     * Sets the "invitable" account types to {@link Contact#mInvitableAccountTypes}.
      */
-    private void loadInvitableAccountTypes(Result contactData) {
-        final ArrayList<AccountType> resultList = Lists.newArrayList();
+    private void loadInvitableAccountTypes(Contact contactData) {
+        final ImmutableList.Builder<AccountType> resultListBuilder =
+                new ImmutableList.Builder<AccountType>();
         if (!contactData.isUserProfile()) {
             Map<AccountTypeWithDataSet, AccountType> invitables =
                     AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes();
@@ -893,26 +466,25 @@
                         Maps.newHashMap(invitables);
 
                 // Remove the ones that already have a raw contact in the current contact
-                for (Entity entity : contactData.getEntities()) {
-                    final ContentValues values = entity.getEntityValues();
+                for (RawContact rawContact : contactData.getRawContacts()) {
                     final AccountTypeWithDataSet type = AccountTypeWithDataSet.get(
-                            values.getAsString(RawContacts.ACCOUNT_TYPE),
-                            values.getAsString(RawContacts.DATA_SET));
+                            rawContact.getAccountTypeString(),
+                            rawContact.getDataSet());
                     resultMap.remove(type);
                 }
 
-                resultList.addAll(resultMap.values());
+                resultListBuilder.addAll(resultMap.values());
             }
         }
 
         // Set to mInvitableAccountTypes
-        contactData.mInvitableAccountTypes = resultList;
+        contactData.setInvitableAccountTypes(resultListBuilder.build());
     }
 
     /**
      * Extracts Contact level columns from the cursor.
      */
-    private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) {
+    private Contact loadContactHeaderData(final Cursor cursor, Uri contactUri) {
         final String directoryParameter =
                 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
         final long directoryId = directoryParameter == null
@@ -943,7 +515,7 @@
             lookupUri = contactUri;
         }
 
-        return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
+        return new Contact(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey,
                 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName,
                 altDisplayName, phoneticName, starred, presence, sendToVoicemail,
                 customRingtone, isUserProfile);
@@ -952,7 +524,7 @@
     /**
      * Extracts RawContact level columns from the cursor.
      */
-    private ContentValues loadRawContact(Cursor cursor) {
+    private ContentValues loadRawContactValues(Cursor cursor) {
         ContentValues cv = new ContentValues();
 
         cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID));
@@ -979,7 +551,7 @@
     /**
      * Extracts Data level columns from the cursor.
      */
-    private ContentValues loadData(Cursor cursor) {
+    private ContentValues loadDataValues(Cursor cursor) {
         ContentValues cv = new ContentValues();
 
         cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID));
@@ -1034,7 +606,7 @@
         }
     }
 
-    private void loadDirectoryMetaData(Result result) {
+    private void loadDirectoryMetaData(Contact result) {
         long directoryId = result.getDirectoryId();
 
         Cursor cursor = getContext().getContentResolver().query(
@@ -1075,14 +647,13 @@
      * Loads groups meta-data for all groups associated with all constituent raw contacts'
      * accounts.
      */
-    private void loadGroupMetaData(Result result) {
+    private void loadGroupMetaData(Contact result) {
         StringBuilder selection = new StringBuilder();
         ArrayList<String> selectionArgs = new ArrayList<String>();
-        for (Entity entity : result.mEntities) {
-            ContentValues values = entity.getEntityValues();
-            String accountName = values.getAsString(RawContacts.ACCOUNT_NAME);
-            String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
-            String dataSet = values.getAsString(RawContacts.DATA_SET);
+        for (RawContact rawContact : result.getRawContacts()) {
+            final String accountName = rawContact.getAccountName();
+            final String accountType = rawContact.getAccountTypeString();
+            final String dataSet = rawContact.getDataSet();
             if (accountName != null && accountType != null) {
                 if (selection.length() != 0) {
                     selection.append(" OR ");
@@ -1101,7 +672,8 @@
                 selection.append(")");
             }
         }
-        final ArrayList<GroupMetaData> groupList = new ArrayList<GroupMetaData>();
+        final ImmutableList.Builder<GroupMetaData> groupListBuilder =
+                new ImmutableList.Builder<GroupMetaData>();
         final Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI,
                 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]),
                 null);
@@ -1119,28 +691,28 @@
                         ? false
                         : cursor.getInt(GroupQuery.FAVORITES) != 0;
 
-                groupList.add(new GroupMetaData(
+                groupListBuilder.add(new GroupMetaData(
                         accountName, accountType, dataSet, groupId, title, defaultGroup,
                         favorites));
             }
         } finally {
             cursor.close();
         }
-        result.mGroups = groupList;
+        result.setGroupMetaData(groupListBuilder.build());
     }
 
     /**
      * Loads all stream items and stream item photos belonging to this contact.
      */
-    private void loadStreamItems(Result result) {
-        Cursor cursor = getContext().getContentResolver().query(
+    private void loadStreamItems(Contact result) {
+        final Cursor cursor = getContext().getContentResolver().query(
                 Contacts.CONTENT_LOOKUP_URI.buildUpon()
                         .appendPath(result.getLookupKey())
                         .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(),
                 null, null, null, null);
-        LongSparseArray<StreamItemEntry> streamItemsById =
+        final LongSparseArray<StreamItemEntry> streamItemsById =
                 new LongSparseArray<StreamItemEntry>();
-        ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
+        final ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>();
         try {
             while (cursor.moveToNext()) {
                 StreamItemEntry streamItem = new StreamItemEntry(cursor);
@@ -1213,11 +785,13 @@
 
         // Set the sorted stream items on the result.
         Collections.sort(streamItems);
-        result.mStreamItems = streamItems;
+        result.setStreamItems(new ImmutableList.Builder<StreamItemEntry>()
+                .addAll(streamItems.iterator())
+                .build());
     }
 
     @Override
-    public void deliverResult(Result result) {
+    public void deliverResult(Contact result) {
         unregisterObserver();
 
         // The creator isn't interested in any further updates
@@ -1254,17 +828,13 @@
      */
     private void postViewNotificationToSyncAdapter() {
         Context context = getContext();
-        for (Entity entity : mContact.getEntities()) {
-            final ContentValues entityValues = entity.getEntityValues();
-            final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID);
+        for (RawContact rawContact : mContact.getRawContacts()) {
+            final long rawContactId = rawContact.getId();
             if (mNotifiedRawContactIds.contains(rawContactId)) {
                 continue; // Already notified for this raw contact.
             }
             mNotifiedRawContactIds.add(rawContactId);
-            final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
-            final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType(
-                    type, dataSet);
+            final AccountType accountType = rawContact.getAccountType();
             final String serviceName = accountType.getViewContactNotifyServiceClassName();
             final String servicePackageName = accountType.getViewContactNotifyServicePackageName();
             if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(servicePackageName)) {
diff --git a/src/com/android/contacts/model/RawContact.java b/src/com/android/contacts/model/RawContact.java
new file mode 100644
index 0000000..3a193b4
--- /dev/null
+++ b/src/com/android/contacts/model/RawContact.java
@@ -0,0 +1,261 @@
+/*
+ * 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.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.net.Uri;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.dataitem.DataItem;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * RawContact represents a single raw contact in the raw contacts database.
+ * It has specialized getters/setters for raw contact
+ * items, and also contains a collection of DataItem objects.  A RawContact contains the information
+ * from a single account.
+ *
+ * This allows RawContact objects to be thought of as a class with raw contact
+ * fields (like account type, name, data set, sync state, etc.) and a list of
+ * DataItem objects that represent contact information elements (like phone
+ * numbers, email, address, etc.).
+ */
+public class RawContact {
+
+    private final Context mContext;
+    private AccountTypeManager mAccountTypeManager;
+    private final ContentValues mValues;
+    private final ArrayList<NamedDataItem> mDataItems;
+
+    public static class NamedDataItem {
+        public final Uri uri;
+        public final DataItem dataItem;
+
+        public NamedDataItem(Uri uri, DataItem dataItem) {
+            this.uri = uri;
+            this.dataItem = dataItem;
+        }
+    }
+
+    public static RawContact createFrom(Entity entity) {
+        final ContentValues values = entity.getEntityValues();
+        final ArrayList<Entity.NamedContentValues> subValues = entity.getSubValues();
+
+        RawContact rawContact = new RawContact(null, values);
+        for (Entity.NamedContentValues subValue : subValues) {
+            rawContact.addNamedDataItemValues(subValue.uri, subValue.values);
+        }
+        return rawContact;
+    }
+
+    /**
+     * A RawContact object can be created with or without a context.
+     *
+     * The context is used for the buildString() member function in DataItem objects,
+     * specifically for retrieving an instance of AccountTypeManager.  It is okay to
+     * pass in null for the context in which case, you will not be able to call buildString(),
+     * getDataKind(), or getAccountType() from a DataItem object.
+     */
+    public RawContact(Context context) {
+        this(context, new ContentValues());
+    }
+
+    public RawContact(Context context, ContentValues values) {
+        mContext = context;
+        mValues = values;
+        mDataItems = new ArrayList<NamedDataItem>();
+    }
+
+    public AccountTypeManager getAccountTypeManager() {
+        if (mAccountTypeManager == null) {
+            mAccountTypeManager = AccountTypeManager.getInstance(mContext);
+        }
+        return mAccountTypeManager;
+    }
+
+    public Context getContext() {
+        return mContext;
+    }
+
+    public ContentValues getValues() {
+        return mValues;
+    }
+
+    /**
+     * Returns the id of the raw contact.
+     */
+    public Long getId() {
+        return getValues().getAsLong(RawContacts._ID);
+    }
+
+    /**
+     * Returns the account name of the raw contact.
+     */
+    public String getAccountName() {
+        return getValues().getAsString(RawContacts.ACCOUNT_NAME);
+    }
+
+    /**
+     * Returns the account type of the raw contact.
+     */
+    public String getAccountTypeString() {
+        return getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+    }
+
+    /**
+     * Returns the data set of the raw contact.
+     */
+    public String getDataSet() {
+        return getValues().getAsString(RawContacts.DATA_SET);
+    }
+
+    /**
+     * Returns the account type and data set of the raw contact.
+     */
+    public String getAccountTypeAndDataSetString() {
+        return getValues().getAsString(RawContacts.ACCOUNT_TYPE_AND_DATA_SET);
+    }
+
+    public boolean isDirty() {
+        return getValues().getAsBoolean(RawContacts.DIRTY);
+    }
+
+    public long getVersion() {
+        return getValues().getAsLong(RawContacts.DIRTY);
+    }
+
+    public String getSourceId() {
+        return getValues().getAsString(RawContacts.SOURCE_ID);
+    }
+
+    public String getSync1() {
+        return getValues().getAsString(RawContacts.SYNC1);
+    }
+
+    public String getSync2() {
+        return getValues().getAsString(RawContacts.SYNC2);
+    }
+
+    public String getSync3() {
+        return getValues().getAsString(RawContacts.SYNC3);
+    }
+
+    public String getSync4() {
+        return getValues().getAsString(RawContacts.SYNC4);
+    }
+
+    public boolean isDeleted() {
+        return getValues().getAsBoolean(RawContacts.DELETED);
+    }
+
+    public boolean isNameVerified() {
+        return getValues().getAsBoolean(RawContacts.NAME_VERIFIED);
+    }
+
+    public long getContactId() {
+        return getValues().getAsLong(Contacts.Entity.CONTACT_ID);
+    }
+
+    public boolean isStarred() {
+        return getValues().getAsBoolean(Contacts.STARRED);
+    }
+
+    public AccountType getAccountType() {
+        return getAccountTypeManager().getAccountType(getAccountTypeString(), getDataSet());
+    }
+
+    /**
+     * Sets the account name, account type, and data set strings.
+     * Valid combinations for account-name, account-type, data-set
+     * 1) null, null, null (local account)
+     * 2) non-null, non-null, null (valid account without data-set)
+     * 3) non-null, non-null, non-null (valid account with data-set)
+     */
+    private void setAccount(String accountName, String accountType, String dataSet) {
+        final ContentValues values = getValues();
+        if (accountName == null) {
+            if (accountType == null && dataSet == null) {
+                // This is a local account
+                values.putNull(RawContacts.ACCOUNT_NAME);
+                values.putNull(RawContacts.ACCOUNT_TYPE);
+                values.putNull(RawContacts.DATA_SET);
+                return;
+            }
+        } else {
+            if (accountType != null) {
+                // This is a valid account, either with or without a dataSet.
+                values.put(RawContacts.ACCOUNT_NAME, accountName);
+                values.put(RawContacts.ACCOUNT_TYPE, accountType);
+                if (dataSet == null) {
+                    values.putNull(RawContacts.DATA_SET);
+                } else {
+                    values.put(RawContacts.DATA_SET, dataSet);
+                }
+            }
+        }
+        throw new IllegalArgumentException(
+                "Not a valid combination of account name, type, and data set.");
+    }
+
+    public void setAccount(AccountWithDataSet accountWithDataSet) {
+        setAccount(accountWithDataSet.name, accountWithDataSet.type, accountWithDataSet.dataSet);
+    }
+
+    public void setAccountToLocal() {
+        setAccount(null, null, null);
+    }
+
+    public void addDataItemValues(ContentValues values) {
+        addNamedDataItemValues(Data.CONTENT_URI, values);
+    }
+
+    public void addNamedDataItemValues(Uri uri, ContentValues values) {
+        mDataItems.add(new NamedDataItem(uri, DataItem.createFrom(this, values)));
+    }
+
+    public List<DataItem> getDataItems() {
+        final ArrayList<DataItem> list = new ArrayList<DataItem>();
+        for (NamedDataItem dataItem : mDataItems) {
+            if (Data.CONTENT_URI.equals(dataItem.uri)) {
+                list.add(dataItem.dataItem);
+            }
+        }
+        return list;
+    }
+
+    public List<NamedDataItem> getNamedDataItems() {
+        return mDataItems;
+    }
+
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("RawContact: ").append(mValues);
+        for (RawContact.NamedDataItem namedDataItem : mDataItems) {
+            sb.append("\n  ").append(namedDataItem.uri);
+            sb.append("\n  -> ").append(namedDataItem.dataItem.getContentValues());
+        }
+        return sb.toString();
+    }
+}
diff --git a/src/com/android/contacts/model/EntityDelta.java b/src/com/android/contacts/model/RawContactDelta.java
similarity index 83%
rename from src/com/android/contacts/model/EntityDelta.java
rename to src/com/android/contacts/model/RawContactDelta.java
index 31b5306..2ee9d5c 100644
--- a/src/com/android/contacts/model/EntityDelta.java
+++ b/src/com/android/contacts/model/RawContactDelta.java
@@ -20,18 +20,22 @@
 import android.content.ContentProviderOperation.Builder;
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.Entity;
-import android.content.Entity.NamedContentValues;
 import android.net.Uri;
 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;
 import android.util.Log;
 
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.dataitem.DataItem;
 import com.android.contacts.test.NeededForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -42,21 +46,20 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-
 /**
- * Contains an {@link Entity} and records any modifications separately so the
- * original {@link Entity} can be swapped out with a newer version and the
+ * Contains a {@link RawContact} and records any modifications separately so the
+ * original {@link RawContact} can be swapped out with a newer version and the
  * changes still cleanly applied.
  * <p>
  * One benefit of this approach is that we can build changes entirely on an
- * empty {@link Entity}, which then becomes an insert {@link RawContacts} case.
+ * empty {@link RawContact}, which then becomes an insert {@link RawContacts} case.
  * <p>
- * When applying modifications over an {@link Entity}, we try finding the
+ * When applying modifications over an {@link RawContact}, we try finding the
  * original {@link Data#_ID} rows where the modifications took place. If those
- * rows are missing from the new {@link Entity}, we know the original data must
+ * rows are missing from the new {@link RawContact}, we know the original data must
  * be deleted, but to preserve the user modifications we treat as an insert.
  */
-public class EntityDelta implements Parcelable {
+public class RawContactDelta implements Parcelable {
     // TODO: optimize by using contentvalues pool, since we allocate so many of them
 
     private static final String TAG = "EntityDelta";
@@ -79,40 +82,40 @@
      */
     private final HashMap<String, ArrayList<ValuesDelta>> mEntries = Maps.newHashMap();
 
-    public EntityDelta() {
+    public RawContactDelta() {
     }
 
-    public EntityDelta(ValuesDelta values) {
+    public RawContactDelta(ValuesDelta values) {
         mValues = values;
     }
 
     /**
-     * Build an {@link EntityDelta} using the given {@link Entity} as a
+     * Build an {@link RawContactDelta} using the given {@link RawContact} as a
      * starting point; the "before" snapshot.
      */
-    public static EntityDelta fromBefore(Entity before) {
-        final EntityDelta entity = new EntityDelta();
-        entity.mValues = ValuesDelta.fromBefore(before.getEntityValues());
-        entity.mValues.setIdColumn(RawContacts._ID);
-        for (NamedContentValues namedValues : before.getSubValues()) {
-            entity.addEntry(ValuesDelta.fromBefore(namedValues.values));
+    public static RawContactDelta fromBefore(RawContact before) {
+        final RawContactDelta rawContactDelta = new RawContactDelta();
+        rawContactDelta.mValues = ValuesDelta.fromBefore(before.getValues());
+        rawContactDelta.mValues.setIdColumn(RawContacts._ID);
+        for (DataItem dataItem : before.getDataItems()) {
+            rawContactDelta.addEntry(ValuesDelta.fromBefore(dataItem.getContentValues()));
         }
-        return entity;
+        return rawContactDelta;
     }
 
     /**
-     * Merge the "after" values from the given {@link EntityDelta} onto the
-     * "before" state represented by this {@link EntityDelta}, discarding any
+     * Merge the "after" values from the given {@link RawContactDelta} onto the
+     * "before" state represented by this {@link RawContactDelta}, discarding any
      * existing "after" states. This is typically used when re-parenting changes
      * onto an updated {@link Entity}.
      */
-    public static EntityDelta mergeAfter(EntityDelta local, EntityDelta remote) {
+    public static RawContactDelta mergeAfter(RawContactDelta local, RawContactDelta remote) {
         // Bail early if trying to merge delete with missing local
         final ValuesDelta remoteValues = remote.mValues;
         if (local == null && (remoteValues.isDelete() || remoteValues.isTransient())) return null;
 
         // Create local version if none exists yet
-        if (local == null) local = new EntityDelta();
+        if (local == null) local = new RawContactDelta();
 
         if (LOGV) {
             final Long localVersion = (local.mValues == null) ? null : local.mValues
@@ -223,6 +226,26 @@
         return getValues().getAsLong(RawContacts._ID);
     }
 
+    public String getAccountName() {
+        return getValues().getAsString(RawContacts.ACCOUNT_NAME);
+    }
+
+    public String getAccountType() {
+        return getValues().getAsString(RawContacts.ACCOUNT_TYPE);
+    }
+
+    public String getDataSet() {
+        return getValues().getAsString(RawContacts.DATA_SET);
+    }
+
+    public AccountType getAccountType(AccountTypeManager manager) {
+        return manager.getAccountType(getAccountType(), getDataSet());
+    }
+
+    public boolean isVisible() {
+        return getValues().isVisible();
+    }
+
     /**
      * Return the list of child {@link ValuesDelta} from our optimized map,
      * creating the list if requested.
@@ -308,8 +331,8 @@
 
     @Override
     public boolean equals(Object object) {
-        if (object instanceof EntityDelta) {
-            final EntityDelta other = (EntityDelta)object;
+        if (object instanceof RawContactDelta) {
+            final RawContactDelta other = (RawContactDelta)object;
 
             // Equality failed if parent values different
             if (!other.mValues.equals(mValues)) return false;
@@ -403,7 +426,7 @@
     /**
      * Build a list of {@link ContentProviderOperation} that will transform the
      * current "before" {@link Entity} state into the modified state which this
-     * {@link EntityDelta} represents.
+     * {@link RawContactDelta} represents.
      */
     public void buildDiff(ArrayList<ContentProviderOperation> buildInto) {
         final int firstIndex = buildInto.size();
@@ -524,15 +547,16 @@
         mContactsQueryUri = Profile.CONTENT_RAW_CONTACTS_URI;
     }
 
-    public static final Parcelable.Creator<EntityDelta> CREATOR = new Parcelable.Creator<EntityDelta>() {
-        public EntityDelta createFromParcel(Parcel in) {
-            final EntityDelta state = new EntityDelta();
+    public static final Parcelable.Creator<RawContactDelta> CREATOR =
+            new Parcelable.Creator<RawContactDelta>() {
+        public RawContactDelta createFromParcel(Parcel in) {
+            final RawContactDelta state = new RawContactDelta();
             state.readFromParcel(in);
             return state;
         }
 
-        public EntityDelta[] newArray(int size) {
-            return new EntityDelta[size];
+        public RawContactDelta[] newArray(int size) {
+            return new RawContactDelta[size];
         }
     };
 
@@ -962,5 +986,118 @@
                 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/EntityDeltaList.java b/src/com/android/contacts/model/RawContactDeltaList.java
similarity index 78%
rename from src/com/android/contacts/model/EntityDeltaList.java
rename to src/com/android/contacts/model/RawContactDeltaList.java
index cf074ef..82dd494 100644
--- a/src/com/android/contacts/model/EntityDeltaList.java
+++ b/src/com/android/contacts/model/RawContactDeltaList.java
@@ -30,7 +30,7 @@
 import android.provider.ContactsContract.RawContacts;
 import android.util.Log;
 
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
@@ -38,40 +38,39 @@
 import java.util.Iterator;
 
 /**
- * Container for multiple {@link EntityDelta} objects, usually when editing
+ * Container for multiple {@link RawContactDelta} objects, usually when editing
  * together as an entire aggregate. Provides convenience methods for parceling
- * and applying another {@link EntityDeltaList} over it.
+ * and applying another {@link RawContactDeltaList} over it.
  */
-public class EntityDeltaList extends ArrayList<EntityDelta> implements Parcelable {
-    private static final String TAG = "EntityDeltaList";
+public class RawContactDeltaList extends ArrayList<RawContactDelta> implements Parcelable {
+    private static final String TAG = RawContactDeltaList.class.getSimpleName();
     private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
 
     private boolean mSplitRawContacts;
     private long[] mJoinWithRawContactIds;
 
-    private EntityDeltaList() {
+    private RawContactDeltaList() {
     }
 
     /**
-     * Create an {@link EntityDeltaList} that contains the given {@link EntityDelta},
+     * Create an {@link RawContactDeltaList} that contains the given {@link RawContactDelta},
      * usually when inserting a new {@link Contacts} entry.
      */
-    public static EntityDeltaList fromSingle(EntityDelta delta) {
-        final EntityDeltaList state = new EntityDeltaList();
+    public static RawContactDeltaList fromSingle(RawContactDelta delta) {
+        final RawContactDeltaList state = new RawContactDeltaList();
         state.add(delta);
         return state;
     }
 
     /**
-     * Create an {@link EntityDeltaList} based on {@link Contacts} specified by the
+     * Create an {@link RawContactDeltaList} based on {@link Contacts} specified by the
      * given query parameters. This closes the {@link EntityIterator} when
      * finished, so it doesn't subscribe to updates.
      */
-    public static EntityDeltaList fromQuery(Uri entityUri, ContentResolver resolver,
+    public static RawContactDeltaList fromQuery(Uri entityUri, ContentResolver resolver,
             String selection, String[] selectionArgs, String sortOrder) {
-        final EntityIterator iterator = RawContacts.newEntityIterator(resolver.query(
-                entityUri, null, selection, selectionArgs,
-                sortOrder));
+        final EntityIterator iterator = RawContacts.newEntityIterator(
+                resolver.query(entityUri, null, selection, selectionArgs, sortOrder));
         try {
             return fromIterator(iterator);
         } finally {
@@ -80,36 +79,41 @@
     }
 
     /**
-     * Create an {@link EntityDeltaList} that contains the entities of the Iterator as before
-     * values.
+     * Create an {@link RawContactDeltaList} that contains the entities of the Iterator as before
+     * values.  This function can be passed an iterator of Entity objects or an iterator of
+     * RawContact objects.
      */
-    public static EntityDeltaList fromIterator(Iterator<Entity> iterator) {
-        final EntityDeltaList state = new EntityDeltaList();
+    public static RawContactDeltaList fromIterator(Iterator<?> iterator) {
+        final RawContactDeltaList state = new RawContactDeltaList();
         // Perform background query to pull contact details
         while (iterator.hasNext()) {
             // Read all contacts into local deltas to prepare for edits
-            final Entity before = iterator.next();
-            final EntityDelta entity = EntityDelta.fromBefore(before);
-            state.add(entity);
+            Object nextObject = iterator.next();
+            final RawContact before = nextObject instanceof Entity
+                    ? RawContact.createFrom((Entity) nextObject)
+                    : (RawContact) nextObject;
+            final RawContactDelta rawContactDelta = RawContactDelta.fromBefore(before);
+            state.add(rawContactDelta);
         }
         return state;
     }
 
     /**
-     * Merge the "after" values from the given {@link EntityDeltaList}, discarding any
+     * Merge the "after" values from the given {@link RawContactDeltaList}, discarding any
      * previous "after" states. This is typically used when re-parenting user
-     * edits onto an updated {@link EntityDeltaList}.
+     * edits onto an updated {@link RawContactDeltaList}.
      */
-    public static EntityDeltaList mergeAfter(EntityDeltaList local, EntityDeltaList remote) {
-        if (local == null) local = new EntityDeltaList();
+    public static RawContactDeltaList mergeAfter(RawContactDeltaList local,
+            RawContactDeltaList remote) {
+        if (local == null) local = new RawContactDeltaList();
 
         // For each entity in the remote set, try matching over existing
-        for (EntityDelta remoteEntity : remote) {
+        for (RawContactDelta remoteEntity : remote) {
             final Long rawContactId = remoteEntity.getValues().getId();
 
             // Find or create local match and merge
-            final EntityDelta localEntity = local.getByRawContactId(rawContactId);
-            final EntityDelta merged = EntityDelta.mergeAfter(localEntity, remoteEntity);
+            final RawContactDelta localEntity = local.getByRawContactId(rawContactId);
+            final RawContactDelta merged = RawContactDelta.mergeAfter(localEntity, remoteEntity);
 
             if (localEntity == null && merged != null) {
                 // No local entry before, so insert
@@ -123,7 +127,7 @@
     /**
      * Build a list of {@link ContentProviderOperation} that will transform all
      * the "before" {@link Entity} states into the modified state which all
-     * {@link EntityDelta} objects represent. This method specifically creates
+     * {@link RawContactDelta} objects represent. This method specifically creates
      * any {@link AggregationExceptions} rules needed to groups edits together.
      */
     public ArrayList<ContentProviderOperation> buildDiff() {
@@ -136,7 +140,7 @@
         int firstInsertRow = -1;
 
         // First pass enforces versions remain consistent
-        for (EntityDelta delta : this) {
+        for (RawContactDelta delta : this) {
             delta.buildAssert(diff);
         }
 
@@ -146,7 +150,7 @@
         int rawContactIndex = 0;
 
         // Second pass builds actual operations
-        for (EntityDelta delta : this) {
+        for (RawContactDelta delta : this) {
             final int firstBatch = diff.size();
             final boolean isInsert = delta.isContactInsert();
             backRefs[rawContactIndex++] = isInsert ? firstBatch : -1;
@@ -281,12 +285,12 @@
     }
 
     /**
-     * Search all contained {@link EntityDelta} for the first one with an
+     * Search all contained {@link RawContactDelta} for the first one with an
      * existing {@link RawContacts#_ID} value. Usually used when creating
      * {@link AggregationExceptions} during an update.
      */
     public long findRawContactId() {
-        for (EntityDelta delta : this) {
+        for (RawContactDelta delta : this) {
             final Long rawContactId = delta.getValues().getAsLong(RawContacts._ID);
             if (rawContactId != null && rawContactId >= 0) {
                 return rawContactId;
@@ -296,11 +300,11 @@
     }
 
     /**
-     * Find {@link RawContacts#_ID} of the requested {@link EntityDelta}.
+     * Find {@link RawContacts#_ID} of the requested {@link RawContactDelta}.
      */
     public Long getRawContactId(int index) {
         if (index >= 0 && index < this.size()) {
-            final EntityDelta delta = this.get(index);
+            final RawContactDelta delta = this.get(index);
             final ValuesDelta values = delta.getValues();
             if (values.isVisible()) {
                 return values.getAsLong(RawContacts._ID);
@@ -310,9 +314,9 @@
     }
 
     /**
-     * Find the raw-contact (an {@link EntityDelta}) with the specified ID.
+     * Find the raw-contact (an {@link RawContactDelta}) with the specified ID.
      */
-    public EntityDelta getByRawContactId(Long rawContactId) {
+    public RawContactDelta getByRawContactId(Long rawContactId) {
         final int index = this.indexOfRawContactId(rawContactId);
         return (index == -1) ? null : this.get(index);
     }
@@ -332,19 +336,21 @@
         return -1;
     }
 
-    /** Return the index of the first EntityDelta corresponding to a writable raw-contact, or -1. */
+    /**
+     * Return the index of the first RawContactDelta corresponding to a writable raw-contact, or -1.
+     * */
     public int indexOfFirstWritableRawContact(Context context) {
         // Find the first writable entity.
         int entityIndex = 0;
-        for (EntityDelta delta : this) {
+        for (RawContactDelta delta : this) {
             if (delta.getRawContactAccountType(context).areContactsWritable()) return entityIndex;
             entityIndex++;
         }
         return -1;
     }
 
-    /**  Return the first EntityDelta corresponding to a writable raw-contact, or null. */
-    public EntityDelta getFirstWritableRawContact(Context context) {
+    /**  Return the first RawContactDelta corresponding to a writable raw-contact, or null. */
+    public RawContactDelta getFirstWritableRawContact(Context context) {
         final int index = indexOfFirstWritableRawContact(context);
         return (index == -1) ? null : get(index);
     }
@@ -352,7 +358,7 @@
     public ValuesDelta getSuperPrimaryEntry(final String mimeType) {
         ValuesDelta primary = null;
         ValuesDelta randomEntry = null;
-        for (EntityDelta delta : this) {
+        for (RawContactDelta delta : this) {
             final ArrayList<ValuesDelta> mimeEntries = delta.getMimeEntries(mimeType);
             if (mimeEntries == null) return null;
 
@@ -404,7 +410,7 @@
     public void writeToParcel(Parcel dest, int flags) {
         final int size = this.size();
         dest.writeInt(size);
-        for (EntityDelta delta : this) {
+        for (RawContactDelta delta : this) {
             dest.writeParcelable(delta, flags);
         }
         dest.writeLongArray(mJoinWithRawContactIds);
@@ -416,24 +422,24 @@
         final ClassLoader loader = getClass().getClassLoader();
         final int size = source.readInt();
         for (int i = 0; i < size; i++) {
-            this.add(source.<EntityDelta> readParcelable(loader));
+            this.add(source.<RawContactDelta> readParcelable(loader));
         }
         mJoinWithRawContactIds = source.createLongArray();
         mSplitRawContacts = source.readInt() != 0;
     }
 
-    public static final Parcelable.Creator<EntityDeltaList> CREATOR =
-            new Parcelable.Creator<EntityDeltaList>() {
+    public static final Parcelable.Creator<RawContactDeltaList> CREATOR =
+            new Parcelable.Creator<RawContactDeltaList>() {
         @Override
-        public EntityDeltaList createFromParcel(Parcel in) {
-            final EntityDeltaList state = new EntityDeltaList();
+        public RawContactDeltaList createFromParcel(Parcel in) {
+            final RawContactDeltaList state = new RawContactDeltaList();
             state.readFromParcel(in);
             return state;
         }
 
         @Override
-        public EntityDeltaList[] newArray(int size) {
-            return new EntityDeltaList[size];
+        public RawContactDeltaList[] newArray(int size) {
+            return new RawContactDeltaList[size];
         }
     };
 
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/RawContactModifier.java
similarity index 91%
rename from src/com/android/contacts/model/EntityModifier.java
rename to src/com/android/contacts/model/RawContactModifier.java
index a26ceca..c397e5f 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/RawContactModifier.java
@@ -49,10 +49,14 @@
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.editor.EventFieldEditorView;
 import com.android.contacts.editor.PhoneticNameEditorView;
-import com.android.contacts.model.AccountType.EditField;
-import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.AccountType.EventEditType;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountType.EditField;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.account.AccountType.EventEditType;
+import com.android.contacts.model.account.GoogleAccountType;
+import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.dataitem.StructuredNameDataItem;
 import com.android.contacts.util.DateUtils;
 import com.android.contacts.util.NameConverter;
 
@@ -68,21 +72,21 @@
 import java.util.Set;
 
 /**
- * Helper methods for modifying an {@link EntityDelta}, such as inserting
+ * Helper methods for modifying an {@link RawContactDelta}, such as inserting
  * new rows, or enforcing {@link AccountType}.
  */
-public class EntityModifier {
-    private static final String TAG = "EntityModifier";
+public class RawContactModifier {
+    private static final String TAG = RawContactModifier.class.getSimpleName();
 
     /** Set to true in order to view logs on entity operations */
     private static final boolean DEBUG = false;
 
     /**
-     * For the given {@link EntityDelta}, determine if the given
+     * For the given {@link RawContactDelta}, determine if the given
      * {@link DataKind} could be inserted under specific
      * {@link AccountType}.
      */
-    public static boolean canInsert(EntityDelta state, DataKind kind) {
+    public static boolean canInsert(RawContactDelta state, DataKind kind) {
         // Insert possible when have valid types and under overall maximum
         final int visibleCount = state.getMimeEntriesCount(kind.mimeType, true);
         final boolean validTypes = hasValidTypes(state, kind);
@@ -91,8 +95,8 @@
         return (validTypes && validOverall);
     }
 
-    public static boolean hasValidTypes(EntityDelta state, DataKind kind) {
-        if (EntityModifier.hasEditTypes(kind)) {
+    public static boolean hasValidTypes(RawContactDelta state, DataKind kind) {
+        if (RawContactModifier.hasEditTypes(kind)) {
             return (getValidTypes(state, kind).size() > 0);
         } else {
             return true;
@@ -101,12 +105,12 @@
 
     /**
      * Ensure that at least one of the given {@link DataKind} exists in the
-     * given {@link EntityDelta} state, and try creating one if none exist.
+     * given {@link RawContactDelta} state, and try creating one if none exist.
      * @return The child (either newly created or the first existing one), or null if the
      *     account doesn't support this {@link DataKind}.
      */
     public static ValuesDelta ensureKindExists(
-            EntityDelta state, AccountType accountType, String mimeType) {
+            RawContactDelta state, AccountType accountType, String mimeType) {
         final DataKind kind = accountType.getKindForMimetype(mimeType);
         final boolean hasChild = state.getMimeEntriesCount(mimeType, true) > 0;
 
@@ -127,16 +131,16 @@
     }
 
     /**
-     * For the given {@link EntityDelta} and {@link DataKind}, return the
+     * For the given {@link RawContactDelta} and {@link DataKind}, return the
      * list possible {@link EditType} options available based on
      * {@link AccountType}.
      */
-    public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind) {
+    public static ArrayList<EditType> getValidTypes(RawContactDelta state, DataKind kind) {
         return getValidTypes(state, kind, null, true, null);
     }
 
     /**
-     * For the given {@link EntityDelta} and {@link DataKind}, return the
+     * For the given {@link RawContactDelta} and {@link DataKind}, return the
      * list possible {@link EditType} options available based on
      * {@link AccountType}.
      *
@@ -144,13 +148,13 @@
      *            list, even when an otherwise-invalid choice. This is useful
      *            when showing a dialog that includes the current type.
      */
-    public static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
+    public static ArrayList<EditType> getValidTypes(RawContactDelta state, DataKind kind,
             EditType forceInclude) {
         return getValidTypes(state, kind, forceInclude, true, null);
     }
 
     /**
-     * For the given {@link EntityDelta} and {@link DataKind}, return the
+     * For the given {@link RawContactDelta} and {@link DataKind}, return the
      * list possible {@link EditType} options available based on
      * {@link AccountType}.
      *
@@ -161,9 +165,9 @@
      *            {@link EditType#secondary}.
      * @param typeCount When provided, will be used for the frequency count of
      *            each {@link EditType}, otherwise built using
-     *            {@link #getTypeFrequencies(EntityDelta, DataKind)}.
+     *            {@link #getTypeFrequencies(RawContactDelta, DataKind)}.
      */
-    private static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
+    private static ArrayList<EditType> getValidTypes(RawContactDelta state, DataKind kind,
             EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) {
         final ArrayList<EditType> validTypes = new ArrayList<EditType>();
 
@@ -197,11 +201,11 @@
 
     /**
      * Count up the frequency that each {@link EditType} appears in the given
-     * {@link EntityDelta}. The returned {@link SparseIntArray} maps from
+     * {@link RawContactDelta}. The returned {@link SparseIntArray} maps from
      * {@link EditType#rawValue} to counts, with the total overall count stored
      * as {@link #FREQUENCY_TOTAL}.
      */
-    private static SparseIntArray getTypeFrequencies(EntityDelta state, DataKind kind) {
+    private static SparseIntArray getTypeFrequencies(RawContactDelta state, DataKind kind) {
         final SparseIntArray typeCount = new SparseIntArray();
 
         // Find all entries for this kind, bailing early if none found
@@ -297,7 +301,7 @@
      * first primary type that doesn't already exist. When all valid types
      * exist, we pick the last valid option.
      */
-    public static EditType getBestValidType(EntityDelta state, DataKind kind,
+    public static EditType getBestValidType(RawContactDelta state, DataKind kind,
             boolean includeSecondary, int exactValue) {
         // Shortcut when no types
         if (kind.typeColumn == null) return null;
@@ -338,10 +342,10 @@
 
     /**
      * Insert a new child of kind {@link DataKind} into the given
-     * {@link EntityDelta}. Tries using the best {@link EditType} found using
-     * {@link #getBestValidType(EntityDelta, DataKind, boolean, int)}.
+     * {@link RawContactDelta}. Tries using the best {@link EditType} found using
+     * {@link #getBestValidType(RawContactDelta, DataKind, boolean, int)}.
      */
-    public static ValuesDelta insertChild(EntityDelta state, DataKind kind) {
+    public static ValuesDelta insertChild(RawContactDelta state, DataKind kind) {
         // First try finding a valid primary
         EditType bestType = getBestValidType(state, kind, false, Integer.MIN_VALUE);
         if (bestType == null) {
@@ -353,9 +357,9 @@
 
     /**
      * Insert a new child of kind {@link DataKind} into the given
-     * {@link EntityDelta}, marked with the given {@link EditType}.
+     * {@link RawContactDelta}, marked with the given {@link EditType}.
      */
-    public static ValuesDelta insertChild(EntityDelta state, DataKind kind, EditType type) {
+    public static ValuesDelta insertChild(RawContactDelta state, DataKind kind, EditType type) {
         // Bail early if invalid kind
         if (kind == null) return null;
         final ContentValues after = new ContentValues();
@@ -379,13 +383,13 @@
     }
 
     /**
-     * Processing to trim any empty {@link ValuesDelta} and {@link EntityDelta}
-     * from the given {@link EntityDeltaList}, assuming the given {@link AccountTypeManager}
+     * Processing to trim any empty {@link ValuesDelta} and {@link RawContactDelta}
+     * from the given {@link RawContactDeltaList}, assuming the given {@link AccountTypeManager}
      * dictates the structure for various fields. This method ignores rows not
      * described by the {@link AccountType}.
      */
-    public static void trimEmpty(EntityDeltaList set, AccountTypeManager accountTypes) {
-        for (EntityDelta state : set) {
+    public static void trimEmpty(RawContactDeltaList set, AccountTypeManager accountTypes) {
+        for (RawContactDelta state : set) {
             ValuesDelta values = state.getValues();
             final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
             final String dataSet = values.getAsString(RawContacts.DATA_SET);
@@ -394,12 +398,12 @@
         }
     }
 
-    public static boolean hasChanges(EntityDeltaList set, AccountTypeManager accountTypes) {
+    public static boolean hasChanges(RawContactDeltaList set, AccountTypeManager accountTypes) {
         if (set.isMarkedForSplitting() || set.isMarkedForJoining()) {
             return true;
         }
 
-        for (EntityDelta state : set) {
+        for (RawContactDelta state : set) {
             ValuesDelta values = state.getValues();
             final String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE);
             final String dataSet = values.getAsString(RawContacts.DATA_SET);
@@ -413,11 +417,11 @@
 
     /**
      * Processing to trim any empty {@link ValuesDelta} rows from the given
-     * {@link EntityDelta}, assuming the given {@link AccountType} dictates
+     * {@link RawContactDelta}, assuming the given {@link AccountType} dictates
      * the structure for various fields. This method ignores rows not described
      * by the {@link AccountType}.
      */
-    public static void trimEmpty(EntityDelta state, AccountType accountType) {
+    public static void trimEmpty(RawContactDelta state, AccountType accountType) {
         boolean hasValues = false;
 
         // Walk through entries for each well-known kind
@@ -440,7 +444,7 @@
                 final boolean isPhoto = TextUtils.equals(Photo.CONTENT_ITEM_TYPE, kind.mimeType);
                 final boolean isGooglePhoto = isPhoto && isGoogleAccount;
 
-                if (EntityModifier.isEmpty(entry, kind) && !isGooglePhoto) {
+                if (RawContactModifier.isEmpty(entry, kind) && !isGooglePhoto) {
                     if (DEBUG) {
                         Log.v(TAG, "Trimming: " + entry.toString());
                     }
@@ -456,7 +460,7 @@
         }
     }
 
-    private static boolean hasChanges(EntityDelta state, AccountType accountType) {
+    private static boolean hasChanges(RawContactDelta state, AccountType accountType) {
         for (DataKind kind : accountType.getSortedDataKinds()) {
             final String mimeType = kind.mimeType;
             final ArrayList<ValuesDelta> entries = state.getMimeEntries(mimeType);
@@ -516,10 +520,10 @@
     }
 
     /**
-     * Parse the given {@link Bundle} into the given {@link EntityDelta} state,
+     * Parse the given {@link Bundle} into the given {@link RawContactDelta} state,
      * assuming the extras defined through {@link Intents}.
      */
-    public static void parseExtras(Context context, AccountType accountType, EntityDelta state,
+    public static void parseExtras(Context context, AccountType accountType, RawContactDelta state,
             Bundle extras) {
         if (extras == null || extras.size() == 0) {
             // Bail early if no useful data
@@ -560,8 +564,8 @@
         final boolean hasOrg = extras.containsKey(Insert.COMPANY)
                 || extras.containsKey(Insert.JOB_TITLE);
         final DataKind kindOrg = accountType.getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
-        if (hasOrg && EntityModifier.canInsert(state, kindOrg)) {
-            final ValuesDelta child = EntityModifier.insertChild(state, kindOrg);
+        if (hasOrg && RawContactModifier.canInsert(state, kindOrg)) {
+            final ValuesDelta child = RawContactModifier.insertChild(state, kindOrg);
 
             final String company = extras.getString(Insert.COMPANY);
             if (ContactsUtils.isGraphic(company)) {
@@ -577,8 +581,8 @@
         // Notes
         final boolean hasNotes = extras.containsKey(Insert.NOTES);
         final DataKind kindNotes = accountType.getKindForMimetype(Note.CONTENT_ITEM_TYPE);
-        if (hasNotes && EntityModifier.canInsert(state, kindNotes)) {
-            final ValuesDelta child = EntityModifier.insertChild(state, kindNotes);
+        if (hasNotes && RawContactModifier.canInsert(state, kindNotes)) {
+            final ValuesDelta child = RawContactModifier.insertChild(state, kindNotes);
 
             final String notes = extras.getString(Insert.NOTES);
             if (ContactsUtils.isGraphic(notes)) {
@@ -594,9 +598,9 @@
     }
 
     private static void parseStructuredNameExtra(
-            Context context, AccountType accountType, EntityDelta state, Bundle extras) {
+            Context context, AccountType accountType, RawContactDelta state, Bundle extras) {
         // StructuredName
-        EntityModifier.ensureKindExists(state, accountType, StructuredName.CONTENT_ITEM_TYPE);
+        RawContactModifier.ensureKindExists(state, accountType, StructuredName.CONTENT_ITEM_TYPE);
         final ValuesDelta child = state.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE);
 
         final String name = extras.getString(Insert.NAME);
@@ -649,7 +653,7 @@
     }
 
     private static void parseStructuredPostalExtra(
-            AccountType accountType, EntityDelta state, Bundle extras) {
+            AccountType accountType, RawContactDelta state, Bundle extras) {
         // StructuredPostal
         final DataKind kind = accountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
         final ValuesDelta child = parseExtras(state, kind, extras, Insert.POSTAL_TYPE,
@@ -675,7 +679,8 @@
     }
 
     private static void parseValues(
-            EntityDelta state, AccountType accountType, ArrayList<ContentValues> dataValueList) {
+            RawContactDelta state, AccountType accountType,
+            ArrayList<ContentValues> dataValueList) {
         for (ContentValues values : dataValueList) {
             String mimeType = values.getAsString(Data.MIMETYPE);
             if (TextUtils.isEmpty(mimeType)) {
@@ -873,7 +878,7 @@
 
     /**
      * Parse a specific entry from the given {@link Bundle} and insert into the
-     * given {@link EntityDelta}. Silently skips the insert when missing value
+     * given {@link RawContactDelta}. Silently skips the insert when missing value
      * or no valid {@link EditType} found.
      *
      * @param typeExtra {@link Bundle} key that holds the incoming
@@ -881,7 +886,7 @@
      * @param valueExtra {@link Bundle} key that holds the incoming value.
      * @param valueColumn Column to write value into {@link ValuesDelta}.
      */
-    public static ValuesDelta parseExtras(EntityDelta state, DataKind kind, Bundle extras,
+    public static ValuesDelta parseExtras(RawContactDelta state, DataKind kind, Bundle extras,
             String typeExtra, String valueExtra, String valueColumn) {
         final CharSequence value = extras.getCharSequence(valueExtra);
 
@@ -889,7 +894,7 @@
         if (kind == null) return null;
 
         // Bail when can't insert type, or value missing
-        final boolean canInsert = EntityModifier.canInsert(state, kind);
+        final boolean canInsert = RawContactModifier.canInsert(state, kind);
         final boolean validValue = (value != null && TextUtils.isGraphic(value));
         if (!validValue || !canInsert) return null;
 
@@ -897,10 +902,10 @@
         final boolean hasType = extras.containsKey(typeExtra);
         final int typeValue = extras.getInt(typeExtra, hasType ? BaseTypes.TYPE_CUSTOM
                 : Integer.MIN_VALUE);
-        final EditType editType = EntityModifier.getBestValidType(state, kind, true, typeValue);
+        final EditType editType = RawContactModifier.getBestValidType(state, kind, true, typeValue);
 
         // Create data row and fill with value
-        final ValuesDelta child = EntityModifier.insertChild(state, kind, editType);
+        final ValuesDelta child = RawContactModifier.insertChild(state, kind, editType);
         child.put(valueColumn, value.toString());
 
         if (editType != null && editType.customColumn != null) {
@@ -937,13 +942,13 @@
     private static final int TYPE_CUSTOM = Phone.TYPE_CUSTOM;
 
     /**
-     * Migrates old EntityDelta to newly created one with a new restriction supplied from
+     * Migrates old RawContactDelta to newly created one with a new restriction supplied from
      * newAccountType.
      *
      * This is only for account switch during account creation (which must be insert operation).
      */
     public static void migrateStateForNewContact(Context context,
-            EntityDelta oldState, EntityDelta newState,
+            RawContactDelta oldState, RawContactDelta newState,
             AccountType oldAccountType, AccountType newAccountType) {
         if (newAccountType == oldAccountType) {
             // Just copying all data in oldState isn't enough, but we can still rely on a lot of
@@ -996,8 +1001,8 @@
      * Checks {@link DataKind#isList} and {@link DataKind#typeOverallMax}, and restricts
      * the number of entries (ValuesDelta) inside newState.
      */
-    private static ArrayList<ValuesDelta> ensureEntryMaxSize(EntityDelta newState, DataKind kind,
-            ArrayList<ValuesDelta> mimeEntries) {
+    private static ArrayList<ValuesDelta> ensureEntryMaxSize(RawContactDelta newState,
+            DataKind kind, ArrayList<ValuesDelta> mimeEntries) {
         if (mimeEntries == null) {
             return null;
         }
@@ -1015,7 +1020,8 @@
 
     /** @hide Public only for testing. */
     public static void migrateStructuredName(
-            Context context, EntityDelta oldState, EntityDelta newState, DataKind newDataKind) {
+            Context context, RawContactDelta oldState, RawContactDelta newState,
+            DataKind newDataKind) {
         final ContentValues values =
                 oldState.getPrimaryEntry(StructuredName.CONTENT_ITEM_TYPE).getAfter();
         if (values == null) {
@@ -1071,24 +1077,24 @@
         if (!TextUtils.isEmpty(phoneticFullName)) {
             if (!supportPhoneticFullName) {
                 // Old data has a phonetic (full) name, while the new account doesn't allow it.
-                final ContentValues tmpValues =
+                final StructuredNameDataItem tmpItem =
                         PhoneticNameEditorView.parsePhoneticName(phoneticFullName, null);
                 values.remove(DataKind.PSEUDO_COLUMN_PHONETIC_NAME);
                 if (supportPhoneticFamilyName) {
                     values.put(StructuredName.PHONETIC_FAMILY_NAME,
-                            tmpValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME));
+                            tmpItem.getPhoneticFamilyName());
                 } else {
                     values.remove(StructuredName.PHONETIC_FAMILY_NAME);
                 }
                 if (supportPhoneticMiddleName) {
                     values.put(StructuredName.PHONETIC_MIDDLE_NAME,
-                            tmpValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME));
+                            tmpItem.getPhoneticMiddleName());
                 } else {
                     values.remove(StructuredName.PHONETIC_MIDDLE_NAME);
                 }
                 if (supportPhoneticGivenName) {
                     values.put(StructuredName.PHONETIC_GIVEN_NAME,
-                            tmpValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME));
+                            tmpItem.getPhoneticGivenName());
                 } else {
                     values.remove(StructuredName.PHONETIC_GIVEN_NAME);
                 }
@@ -1117,7 +1123,7 @@
     }
 
     /** @hide Public only for testing. */
-    public static void migratePostal(EntityDelta oldState, EntityDelta newState,
+    public static void migratePostal(RawContactDelta oldState, RawContactDelta newState,
             DataKind newDataKind) {
         final ArrayList<ValuesDelta> mimeEntries = ensureEntryMaxSize(newState, newDataKind,
                 oldState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE));
@@ -1228,7 +1234,7 @@
     }
 
     /** @hide Public only for testing. */
-    public static void migrateEvent(EntityDelta oldState, EntityDelta newState,
+    public static void migrateEvent(RawContactDelta oldState, RawContactDelta newState,
             DataKind newDataKind, Integer defaultYear) {
         final ArrayList<ValuesDelta> mimeEntries = ensureEntryMaxSize(newState, newDataKind,
                 oldState.getMimeEntries(Event.CONTENT_ITEM_TYPE));
@@ -1285,7 +1291,7 @@
 
     /** @hide Public only for testing. */
     public static void migrateGenericWithoutTypeColumn(
-            EntityDelta oldState, EntityDelta newState, DataKind newDataKind) {
+            RawContactDelta oldState, RawContactDelta newState, DataKind newDataKind) {
         final ArrayList<ValuesDelta> mimeEntries = ensureEntryMaxSize(newState, newDataKind,
                 oldState.getMimeEntries(newDataKind.mimeType));
         if (mimeEntries == null || mimeEntries.isEmpty()) {
@@ -1302,7 +1308,7 @@
 
     /** @hide Public only for testing. */
     public static void migrateGenericWithTypeColumn(
-            EntityDelta oldState, EntityDelta newState, DataKind newDataKind) {
+            RawContactDelta oldState, RawContactDelta newState, DataKind newDataKind) {
         final ArrayList<ValuesDelta> mimeEntries = oldState.getMimeEntries(newDataKind.mimeType);
         if (mimeEntries == null || mimeEntries.isEmpty()) {
             return;
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/account/AccountType.java
similarity index 98%
rename from src/com/android/contacts/model/AccountType.java
rename to src/com/android/contacts/model/account/AccountType.java
index f3aa868..edd17a0 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/account/AccountType.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -28,6 +28,8 @@
 import android.widget.EditText;
 
 import com.android.contacts.R;
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.dataitem.DataKind;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
diff --git a/src/com/android/contacts/model/AccountTypeWithDataSet.java b/src/com/android/contacts/model/account/AccountTypeWithDataSet.java
similarity index 98%
rename from src/com/android/contacts/model/AccountTypeWithDataSet.java
rename to src/com/android/contacts/model/account/AccountTypeWithDataSet.java
index 8d55758..ab0a891 100644
--- a/src/com/android/contacts/model/AccountTypeWithDataSet.java
+++ b/src/com/android/contacts/model/account/AccountTypeWithDataSet.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.Context;
 import android.database.Cursor;
diff --git a/src/com/android/contacts/model/AccountWithDataSet.java b/src/com/android/contacts/model/account/AccountWithDataSet.java
similarity index 99%
rename from src/com/android/contacts/model/AccountWithDataSet.java
rename to src/com/android/contacts/model/account/AccountWithDataSet.java
index 2a8fac7..03fcc02 100644
--- a/src/com/android/contacts/model/AccountWithDataSet.java
+++ b/src/com/android/contacts/model/account/AccountWithDataSet.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.accounts.Account;
 import android.content.Context;
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/account/BaseAccountType.java
similarity index 99%
rename from src/com/android/contacts/model/BaseAccountType.java
rename to src/com/android/contacts/model/account/BaseAccountType.java
index f67ad53..7f9e1ef 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/account/BaseAccountType.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -39,6 +39,7 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.contacts.R;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.test.NeededForTesting;
 import com.android.contacts.util.DateUtils;
 import com.google.common.collect.Lists;
diff --git a/src/com/android/contacts/model/ExchangeAccountType.java b/src/com/android/contacts/model/account/ExchangeAccountType.java
similarity index 99%
rename from src/com/android/contacts/model/ExchangeAccountType.java
rename to src/com/android/contacts/model/account/ExchangeAccountType.java
index 6c73568..5ca3308 100644
--- a/src/com/android/contacts/model/ExchangeAccountType.java
+++ b/src/com/android/contacts/model/account/ExchangeAccountType.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -32,6 +32,7 @@
 import android.util.Log;
 
 import com.android.contacts.R;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.DateUtils;
 import com.google.common.collect.Lists;
 
diff --git a/src/com/android/contacts/model/ExternalAccountType.java b/src/com/android/contacts/model/account/ExternalAccountType.java
similarity index 99%
rename from src/com/android/contacts/model/ExternalAccountType.java
rename to src/com/android/contacts/model/account/ExternalAccountType.java
index 3aca24f..71dbebf 100644
--- a/src/com/android/contacts/model/ExternalAccountType.java
+++ b/src/com/android/contacts/model/account/ExternalAccountType.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -31,6 +31,7 @@
 import android.util.Log;
 import android.util.Xml;
 
+import com.android.contacts.model.dataitem.DataKind;
 import com.google.common.annotations.VisibleForTesting;
 
 import org.xmlpull.v1.XmlPullParser;
diff --git a/src/com/android/contacts/model/FallbackAccountType.java b/src/com/android/contacts/model/account/FallbackAccountType.java
similarity index 96%
rename from src/com/android/contacts/model/FallbackAccountType.java
rename to src/com/android/contacts/model/account/FallbackAccountType.java
index 21eb9e7..dae288d 100644
--- a/src/com/android/contacts/model/FallbackAccountType.java
+++ b/src/com/android/contacts/model/account/FallbackAccountType.java
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.Context;
 import android.util.Log;
 
 import com.android.contacts.R;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.test.NeededForTesting;
 
 public class FallbackAccountType extends BaseAccountType {
diff --git a/src/com/android/contacts/model/GoogleAccountType.java b/src/com/android/contacts/model/account/GoogleAccountType.java
similarity index 98%
rename from src/com/android/contacts/model/GoogleAccountType.java
rename to src/com/android/contacts/model/account/GoogleAccountType.java
index d7360ee..192c3d0 100644
--- a/src/com/android/contacts/model/GoogleAccountType.java
+++ b/src/com/android/contacts/model/account/GoogleAccountType.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.ContentValues;
 import android.content.Context;
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import com.android.contacts.R;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.util.DateUtils;
 import com.google.common.collect.Lists;
 
diff --git a/src/com/android/contacts/model/dataitem/DataItem.java b/src/com/android/contacts/model/dataitem/DataItem.java
new file mode 100644
index 0000000..25c44cb
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/DataItem.java
@@ -0,0 +1,214 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Identity;
+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.provider.ContactsContract.Contacts.Data;
+
+import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.RawContact;
+import com.android.contacts.model.account.AccountType;
+
+/**
+ * This is the base class for data items, which represents a row from the Data table.
+ */
+public class DataItem {
+
+    private final ContentValues mContentValues;
+
+    /**
+     * The raw contact that this data item is associated with.  This can be null.
+     */
+    private final RawContact mRawContact;
+    private DataKind mDataKind;
+
+    protected DataItem(RawContact rawContact, ContentValues values) {
+        mContentValues = values;
+        mRawContact = rawContact;
+    }
+
+    /**
+     * Factory for creating subclasses of DataItem objects based on the mimetype in the
+     * content values.  Raw contact is the raw contact that this data item is associated with.
+     */
+    public static DataItem createFrom(RawContact rawContact, ContentValues values) {
+        final String mimeType = values.getAsString(Data.MIMETYPE);
+        if (GroupMembership.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new GroupMembershipDataItem(rawContact, values);
+        } else if (StructuredName.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new StructuredNameDataItem(rawContact, values);
+        } else if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new PhoneDataItem(rawContact, values);
+        } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new EmailDataItem(rawContact, values);
+        } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new StructuredPostalDataItem(rawContact, values);
+        } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new ImDataItem(rawContact, values);
+        } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new OrganizationDataItem(rawContact, values);
+        } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new NicknameDataItem(rawContact, values);
+        } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new NoteDataItem(rawContact, values);
+        } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new WebsiteDataItem(rawContact, values);
+        } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new SipAddressDataItem(rawContact, values);
+        } else if (Event.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new EventDataItem(rawContact, values);
+        } else if (Relation.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new RelationDataItem(rawContact, values);
+        } else if (Identity.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new IdentityDataItem(rawContact, values);
+        } else if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) {
+            return new PhotoDataItem(rawContact, values);
+        }
+
+        // generic
+        return new DataItem(rawContact, values);
+    }
+
+    public ContentValues getContentValues() {
+        return mContentValues;
+    }
+
+    protected RawContact getRawContact() {
+        return mRawContact;
+    }
+
+    public void setRawContactId(long rawContactId) {
+        mContentValues.put(Data.RAW_CONTACT_ID, rawContactId);
+    }
+
+    /**
+     * Returns the data id.
+     */
+    public long getId() {
+        return mContentValues.getAsLong(Data._ID);
+    }
+
+    public long getRawContactId() {
+        return mContentValues.getAsLong(Data.RAW_CONTACT_ID);
+    }
+    /**
+     * Returns the mimetype of the data.
+     */
+    public String getMimeType() {
+        return mContentValues.getAsString(Data.MIMETYPE);
+    }
+
+    public void setMimeType(String mimeType) {
+        mContentValues.put(Data.MIMETYPE, mimeType);
+    }
+
+    public boolean isPrimary() {
+        Integer primary = mContentValues.getAsInteger(Data.IS_PRIMARY);
+        return primary != null && primary != 0;
+    }
+
+    public boolean isSuperPrimary() {
+        Integer superPrimary = mContentValues.getAsInteger(Data.IS_SUPER_PRIMARY);
+        return superPrimary != null && superPrimary != 0;
+    }
+
+    public int getDataVersion() {
+        return mContentValues.getAsInteger(Data.DATA_VERSION);
+    }
+
+    public AccountTypeManager getAccountTypeManager() {
+        if (mRawContact == null) {
+            return null;
+        } else {
+            return mRawContact.getAccountTypeManager();
+        }
+    }
+
+    public AccountType getAccountType() {
+        if (mRawContact == null) {
+            return null;
+        } else {
+            return mRawContact.getAccountType();
+        }
+    }
+
+    /**
+     * This method can only be invoked if the raw contact is non-null.
+     */
+    public DataKind getDataKind() {
+        if (mRawContact == null) {
+            throw new IllegalStateException("mRawContact must be non-null to call getDataKind()");
+        }
+
+        if (mDataKind == null) {
+            mDataKind = getAccountTypeManager().getKindOrFallback(
+                    mRawContact.getAccountTypeString(), mRawContact.getDataSet(), getMimeType());
+        }
+
+        return mDataKind;
+    }
+
+    public boolean hasKindTypeColumn() {
+        final String key = getDataKind().typeColumn;
+        return key != null && mContentValues.containsKey(key);
+    }
+
+    public int getKindTypeColumn() {
+        final String key = getDataKind().typeColumn;
+        return mContentValues.getAsInteger(key);
+    }
+
+    /**
+     * This builds the data string depending on the type of data item by using the generic
+     * DataKind object underneath.  This DataItem object must be associated with a raw contact
+     * for this function to work.
+     */
+    public String buildDataString() {
+        if (mRawContact == null) {
+            throw new IllegalStateException("mRawContact must be non-null to call getDataKind()");
+        }
+        final DataKind kind = getDataKind();
+
+        if (kind.actionBody == null) {
+            return null;
+        }
+        CharSequence actionBody = kind.actionBody.inflateUsing(mRawContact.getContext(),
+                mContentValues);
+        return actionBody == null ? null : actionBody.toString();
+    }
+
+    public String getKindString() {
+        final DataKind kind = getDataKind();
+        return (kind.titleRes == -1 || kind.titleRes == 0) ? ""
+                : mRawContact.getContext().getString(kind.titleRes);
+    }
+}
diff --git a/src/com/android/contacts/model/DataKind.java b/src/com/android/contacts/model/dataitem/DataKind.java
similarity index 95%
rename from src/com/android/contacts/model/DataKind.java
rename to src/com/android/contacts/model/dataitem/DataKind.java
index bae9836..8707012 100644
--- a/src/com/android/contacts/model/DataKind.java
+++ b/src/com/android/contacts/model/dataitem/DataKind.java
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.dataitem;
 
 import android.content.ContentValues;
 import android.provider.ContactsContract.Data;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType.EditField;
-import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.AccountType.StringInflater;
+import com.android.contacts.model.account.AccountType.EditField;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.account.AccountType.StringInflater;
 import com.google.common.collect.Iterators;
 
 import java.text.SimpleDateFormat;
diff --git a/src/com/android/contacts/model/dataitem/EmailDataItem.java b/src/com/android/contacts/model/dataitem/EmailDataItem.java
new file mode 100644
index 0000000..a535c73
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/EmailDataItem.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents an email data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Email}.
+ */
+public class EmailDataItem extends DataItem {
+
+    /* package */ EmailDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getAddress() {
+        return getContentValues().getAsString(Email.ADDRESS);
+    }
+
+    public String getDisplayName() {
+        return getContentValues().getAsString(Email.DISPLAY_NAME);
+    }
+
+    public String getData() {
+        return getContentValues().getAsString(Email.DATA);
+    }
+
+    /**
+     * Values is one of Email.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Email.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Email.LABEL);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/EventDataItem.java b/src/com/android/contacts/model/dataitem/EventDataItem.java
new file mode 100644
index 0000000..2114279
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/EventDataItem.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Event;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents an event data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Event}.
+ */
+public class EventDataItem extends DataItem {
+
+    /* package */ EventDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getStartDate() {
+        return getContentValues().getAsString(Event.START_DATE);
+    }
+
+    /**
+     * Values are one of Event.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Event.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Event.LABEL);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/GroupMembershipDataItem.java b/src/com/android/contacts/model/dataitem/GroupMembershipDataItem.java
new file mode 100644
index 0000000..aea9bca
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/GroupMembershipDataItem.java
@@ -0,0 +1,42 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a group memebership data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.GroupMembership}.
+ */
+public class GroupMembershipDataItem extends DataItem {
+
+    /* package */ GroupMembershipDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public long getGroupRowId() {
+        return getContentValues().getAsLong(GroupMembership.GROUP_ROW_ID);
+    }
+
+    public String getGroupSourceId() {
+        return getContentValues().getAsString(GroupMembership.GROUP_SOURCE_ID);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/IdentityDataItem.java b/src/com/android/contacts/model/dataitem/IdentityDataItem.java
new file mode 100644
index 0000000..fd4b836
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/IdentityDataItem.java
@@ -0,0 +1,42 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Identity;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents an identity data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Identity}.
+ */
+public class IdentityDataItem extends DataItem {
+
+    /* package */ IdentityDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getIdentity() {
+        return getContentValues().getAsString(Identity.IDENTITY);
+    }
+
+    public String getNamespace() {
+        return getContentValues().getAsString(Identity.NAMESPACE);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/ImDataItem.java b/src/com/android/contacts/model/dataitem/ImDataItem.java
new file mode 100644
index 0000000..3a08325
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/ImDataItem.java
@@ -0,0 +1,94 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents an IM data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Im}.
+ */
+public class ImDataItem extends DataItem {
+
+    private final boolean mCreatedFromEmail;
+
+    /* package */ ImDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+        mCreatedFromEmail = false;
+    }
+
+    private ImDataItem(RawContact rawContact, ContentValues values,
+            boolean createdFromEmail) {
+        super(rawContact, values);
+        mCreatedFromEmail = createdFromEmail;
+    }
+
+    public static ImDataItem createFromEmail(EmailDataItem item) {
+        ImDataItem im = new ImDataItem(item.getRawContact(),
+                new ContentValues(item.getContentValues()), true);
+        im.setMimeType(Im.CONTENT_ITEM_TYPE);
+        return im;
+    }
+
+    public String getData() {
+        if (mCreatedFromEmail) {
+            return getContentValues().getAsString(Email.DATA);
+        } else {
+            return getContentValues().getAsString(Im.DATA);
+        }
+    }
+
+    /**
+     * Values are one of Im.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Im.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Im.LABEL);
+    }
+
+    /**
+     * Values are one of Im.PROTOCOL_
+     */
+    public Integer getProtocol() {
+        return getContentValues().getAsInteger(Im.PROTOCOL);
+    }
+
+    public boolean isProtocolValid() {
+        return getProtocol() != null;
+    }
+
+    public String getCustomProtocol() {
+        return getContentValues().getAsString(Im.CUSTOM_PROTOCOL);
+    }
+
+    public int getChatCapability() {
+        Integer result = getContentValues().getAsInteger(Im.CHAT_CAPABILITY);
+        return result == null ? 0 : result;
+    }
+
+    public boolean isCreatedFromEmail() {
+        return mCreatedFromEmail;
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/NicknameDataItem.java b/src/com/android/contacts/model/dataitem/NicknameDataItem.java
new file mode 100644
index 0000000..7b52510
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/NicknameDataItem.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Nickname;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a nickname data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Nickname}.
+ */
+public class NicknameDataItem extends DataItem {
+
+    public NicknameDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getName() {
+        return getContentValues().getAsString(Nickname.NAME);
+    }
+
+    /**
+     * Types are defined as Nickname.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Nickname.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Nickname.LABEL);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/NoteDataItem.java b/src/com/android/contacts/model/dataitem/NoteDataItem.java
new file mode 100644
index 0000000..0d0fa24
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/NoteDataItem.java
@@ -0,0 +1,38 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Note;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a note data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Note}.
+ */
+public class NoteDataItem extends DataItem {
+
+    /* package */ NoteDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getNote() {
+        return getContentValues().getAsString(Note.NOTE);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/OrganizationDataItem.java b/src/com/android/contacts/model/dataitem/OrganizationDataItem.java
new file mode 100644
index 0000000..1326bdb
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/OrganizationDataItem.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents an organization data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Organization}.
+ */
+public class OrganizationDataItem extends DataItem {
+
+    /* package */ OrganizationDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getCompany() {
+        return getContentValues().getAsString(Organization.COMPANY);
+    }
+
+    /**
+     * Values are one of Organization.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Organization.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Organization.LABEL);
+    }
+
+    public String getTitle() {
+        return getContentValues().getAsString(Organization.TITLE);
+    }
+
+    public String getDepartment() {
+        return getContentValues().getAsString(Organization.DEPARTMENT);
+    }
+
+    public String getJobDescription() {
+        return getContentValues().getAsString(Organization.JOB_DESCRIPTION);
+    }
+
+    public String getSymbol() {
+        return getContentValues().getAsString(Organization.SYMBOL);
+    }
+
+    public String getPhoneticName() {
+        return getContentValues().getAsString(Organization.PHONETIC_NAME);
+    }
+
+    public String getOfficeLocation() {
+        return getContentValues().getAsString(Organization.OFFICE_LOCATION);
+    }
+
+    public String getPhoneticNameStyle() {
+        return getContentValues().getAsString(Organization.PHONETIC_NAME_STYLE);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/PhoneDataItem.java b/src/com/android/contacts/model/dataitem/PhoneDataItem.java
new file mode 100644
index 0000000..94a0054
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/PhoneDataItem.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a phone data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Phone}.
+ */
+public class PhoneDataItem extends DataItem {
+
+    /* package */ PhoneDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getNumber() {
+        return getContentValues().getAsString(Phone.NUMBER);
+    }
+
+    /**
+     * Returns the normalized phone number in E164 format.
+     */
+    public String getNormalizedNumber() {
+        return getContentValues().getAsString(Phone.NORMALIZED_NUMBER);
+    }
+
+    /**
+     * Values are Phone.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Phone.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Phone.LABEL);
+    }
+
+}
diff --git a/src/com/android/contacts/model/dataitem/PhotoDataItem.java b/src/com/android/contacts/model/dataitem/PhotoDataItem.java
new file mode 100644
index 0000000..5e355fa
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/PhotoDataItem.java
@@ -0,0 +1,42 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts.Photo;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a photo data item, wrapping the columns in
+ * {@link ContactsContract.Contacts.Photo}.
+ */
+public class PhotoDataItem extends DataItem {
+
+    /* package */ PhotoDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public long getPhotoFileId() {
+        return getContentValues().getAsLong(Photo.PHOTO_FILE_ID);
+    }
+
+    public byte[] getPhoto() {
+        return getContentValues().getAsByteArray(Photo.PHOTO);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/RelationDataItem.java b/src/com/android/contacts/model/dataitem/RelationDataItem.java
new file mode 100644
index 0000000..7c22cf5
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/RelationDataItem.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Relation;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a relation data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Relation}.
+ */
+public class RelationDataItem extends DataItem {
+
+    /* package */ RelationDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getName() {
+        return getContentValues().getAsString(Relation.NAME);
+    }
+
+    /**
+     * Values are one of Relation.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Relation.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Relation.LABEL);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/SipAddressDataItem.java b/src/com/android/contacts/model/dataitem/SipAddressDataItem.java
new file mode 100644
index 0000000..6b8e93d
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/SipAddressDataItem.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a sip address data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.SipAddress}.
+ */
+public class SipAddressDataItem extends DataItem {
+
+    /* package */ SipAddressDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getSipAddress() {
+        return getContentValues().getAsString(SipAddress.SIP_ADDRESS);
+    }
+
+    /**
+     * Value is one of SipAddress.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(SipAddress.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(SipAddress.LABEL);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/StructuredNameDataItem.java b/src/com/android/contacts/model/dataitem/StructuredNameDataItem.java
new file mode 100644
index 0000000..4654a6f
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/StructuredNameDataItem.java
@@ -0,0 +1,100 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts.Data;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a structured name data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.StructuredName}.
+ */
+public class StructuredNameDataItem extends DataItem {
+
+    public StructuredNameDataItem() {
+        super(null, new ContentValues());
+        getContentValues().put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+    }
+
+    /* package */ StructuredNameDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getDisplayName() {
+        return getContentValues().getAsString(StructuredName.DISPLAY_NAME);
+    }
+
+    public void setDisplayName(String name) {
+        getContentValues().put(StructuredName.DISPLAY_NAME, name);
+    }
+
+    public String getGivenName() {
+        return getContentValues().getAsString(StructuredName.GIVEN_NAME);
+    }
+
+    public String getFamilyName() {
+        return getContentValues().getAsString(StructuredName.FAMILY_NAME);
+    }
+
+    public String getPrefix() {
+        return getContentValues().getAsString(StructuredName.PREFIX);
+    }
+
+    public String getMiddleName() {
+        return getContentValues().getAsString(StructuredName.MIDDLE_NAME);
+    }
+
+    public String getSuffix() {
+        return getContentValues().getAsString(StructuredName.SUFFIX);
+    }
+
+    public String getPhoneticGivenName() {
+        return getContentValues().getAsString(StructuredName.PHONETIC_GIVEN_NAME);
+    }
+
+    public String getPhoneticMiddleName() {
+        return getContentValues().getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
+    }
+
+    public String getPhoneticFamilyName() {
+        return getContentValues().getAsString(StructuredName.PHONETIC_FAMILY_NAME);
+    }
+
+    public String getFullNameStyle() {
+        return getContentValues().getAsString(StructuredName.FULL_NAME_STYLE);
+    }
+
+    public String getPhoneticNameStyle() {
+        return getContentValues().getAsString(StructuredName.PHONETIC_NAME_STYLE);
+    }
+
+    public void setPhoneticFamilyName(String name) {
+        getContentValues().put(StructuredName.PHONETIC_FAMILY_NAME, name);
+    }
+
+    public void setPhoneticMiddleName(String name) {
+        getContentValues().put(StructuredName.PHONETIC_MIDDLE_NAME, name);
+    }
+
+    public void setPhoneticGivenName(String name) {
+        getContentValues().put(StructuredName.PHONETIC_GIVEN_NAME, name);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/StructuredPostalDataItem.java b/src/com/android/contacts/model/dataitem/StructuredPostalDataItem.java
new file mode 100644
index 0000000..cc2cf56
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/StructuredPostalDataItem.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a structured postal data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.StructuredPostal}.
+ */
+public class StructuredPostalDataItem extends DataItem {
+
+    /* package */ StructuredPostalDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getFormattedAddress() {
+        return getContentValues().getAsString(StructuredPostal.FORMATTED_ADDRESS);
+    }
+
+    /**
+     * Values are one of StructuredPostal.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(StructuredPostal.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(StructuredPostal.LABEL);
+    }
+
+    public String getStreet() {
+        return getContentValues().getAsString(StructuredPostal.STREET);
+    }
+
+    public String getPOBox() {
+        return getContentValues().getAsString(StructuredPostal.POBOX);
+    }
+
+    public String getNeighborhood() {
+        return getContentValues().getAsString(StructuredPostal.NEIGHBORHOOD);
+    }
+
+    public String getCity() {
+        return getContentValues().getAsString(StructuredPostal.CITY);
+    }
+
+    public String getRegion() {
+        return getContentValues().getAsString(StructuredPostal.REGION);
+    }
+
+    public String getPostcode() {
+        return getContentValues().getAsString(StructuredPostal.POSTCODE);
+    }
+
+    public String getCountry() {
+        return getContentValues().getAsString(StructuredPostal.COUNTRY);
+    }
+}
diff --git a/src/com/android/contacts/model/dataitem/WebsiteDataItem.java b/src/com/android/contacts/model/dataitem/WebsiteDataItem.java
new file mode 100644
index 0000000..c3aadf3
--- /dev/null
+++ b/src/com/android/contacts/model/dataitem/WebsiteDataItem.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dataitem;
+
+import android.content.ContentValues;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Website;
+
+import com.android.contacts.model.RawContact;
+
+/**
+ * Represents a website data item, wrapping the columns in
+ * {@link ContactsContract.CommonDataKinds.Website}.
+ */
+public class WebsiteDataItem extends DataItem {
+
+    /* package */ WebsiteDataItem(RawContact rawContact, ContentValues values) {
+        super(rawContact, values);
+    }
+
+    public String getUrl() {
+        return getContentValues().getAsString(Website.URL);
+    }
+
+    /**
+     * Value is one of Website.TYPE_*
+     */
+    public int getType() {
+        return getContentValues().getAsInteger(Website.TYPE);
+    }
+
+    public String getLabel() {
+        return getContentValues().getAsString(Website.LABEL);
+    }
+}
diff --git a/src/com/android/contacts/quickcontact/DataAction.java b/src/com/android/contacts/quickcontact/DataAction.java
index ec6bf4c..c10c338 100644
--- a/src/com/android/contacts/quickcontact/DataAction.java
+++ b/src/com/android/contacts/quickcontact/DataAction.java
@@ -17,27 +17,28 @@
 package com.android.contacts.quickcontact;
 
 import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.WebAddress;
-import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.SipAddress;
-import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.provider.ContactsContract.Data;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType.EditType;
-import com.android.contacts.model.DataKind;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.dataitem.EmailDataItem;
+import com.android.contacts.model.dataitem.ImDataItem;
+import com.android.contacts.model.dataitem.PhoneDataItem;
+import com.android.contacts.model.dataitem.SipAddressDataItem;
+import com.android.contacts.model.dataitem.StructuredPostalDataItem;
+import com.android.contacts.model.dataitem.WebsiteDataItem;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.util.StructuredPostalUtils;
@@ -68,51 +69,45 @@
     /**
      * Create an action from common {@link Data} elements.
      */
-    public DataAction(Context context, String mimeType, DataKind kind, long dataId,
-            ContentValues entryValues) {
+    public DataAction(Context context, DataItem item) {
         mContext = context;
-        mKind = kind;
-        mMimeType = mimeType;
+        mKind = item.getDataKind();
+        mMimeType = item.getMimeType();
 
         // Determine type for subtitle
         mSubtitle = "";
-        if (kind.typeColumn != null) {
-            if (entryValues.containsKey(kind.typeColumn)) {
-                final int typeValue = entryValues.getAsInteger(kind.typeColumn);
+        if (item.hasKindTypeColumn()) {
+            final int typeValue = item.getKindTypeColumn();
 
-                // get type string
-                for (EditType type : kind.typeList) {
-                    if (type.rawValue == typeValue) {
-                        if (type.customColumn == null) {
-                            // Non-custom type. Get its description from the resource
-                            mSubtitle = context.getString(type.labelRes);
-                        } else {
-                            // Custom type. Read it from the database
-                            mSubtitle = entryValues.getAsString(type.customColumn);
-                        }
-                        break;
+            // get type string
+            for (EditType type : item.getDataKind().typeList) {
+                if (type.rawValue == typeValue) {
+                    if (type.customColumn == null) {
+                        // Non-custom type. Get its description from the resource
+                        mSubtitle = context.getString(type.labelRes);
+                    } else {
+                        // Custom type. Read it from the database
+                        mSubtitle = item.getContentValues().getAsString(type.customColumn);
                     }
+                    break;
                 }
             }
         }
 
-        final Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
-        mIsPrimary = superPrimary != null && superPrimary != 0;
+        mIsPrimary = item.isSuperPrimary();
+        mBody = item.buildDataString();
 
-        if (mKind.actionBody != null) {
-            mBody = mKind.actionBody.inflateUsing(context, entryValues);
-        }
-
-        mDataId = dataId;
-        mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, dataId);
+        mDataId = item.getId();
+        mDataUri = ContentUris.withAppendedId(Data.CONTENT_URI, mDataId);
 
         final boolean hasPhone = PhoneCapabilityTester.isPhone(mContext);
         final boolean hasSms = PhoneCapabilityTester.isSmsIntentRegistered(mContext);
 
         // Handle well-known MIME-types with special care
-        if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
+        if (item instanceof PhoneDataItem) {
             if (PhoneCapabilityTester.isPhone(mContext)) {
-                final String number = entryValues.getAsString(Phone.NUMBER);
+                PhoneDataItem phone = (PhoneDataItem) item;
+                final String number = phone.getNumber();
                 if (!TextUtils.isEmpty(number)) {
 
                     final Intent phoneIntent = hasPhone ? ContactsUtils.getCallIntent(number)
@@ -124,8 +119,8 @@
                     if (hasPhone && hasSms) {
                         mIntent = phoneIntent;
                         mAlternateIntent = smsIntent;
-                        mAlternateIconRes = kind.iconAltRes;
-                        mAlternateIconDescriptionRes = kind.iconAltDescriptionRes;
+                        mAlternateIconRes = phone.getDataKind().iconAltRes;
+                        mAlternateIconDescriptionRes = phone.getDataKind().iconAltDescriptionRes;
                     } else if (hasPhone) {
                         mIntent = phoneIntent;
                     } else if (hasSms) {
@@ -133,9 +128,10 @@
                     }
                 }
             }
-        } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType)) {
+        } else if (item instanceof SipAddressDataItem) {
             if (PhoneCapabilityTester.isSipPhone(mContext)) {
-                final String address = entryValues.getAsString(SipAddress.SIP_ADDRESS);
+                final SipAddressDataItem sip = (SipAddressDataItem) item;
+                final String address = sip.getSipAddress();
                 if (!TextUtils.isEmpty(address)) {
                     final Uri callUri = Uri.fromParts(Constants.SCHEME_SIP, address, null);
                     mIntent = ContactsUtils.getCallIntent(callUri);
@@ -147,26 +143,27 @@
                     // for the SIP-related intent-filters in its manifest.
                 }
             }
-        } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
-            final String address = entryValues.getAsString(Email.DATA);
+        } else if (item instanceof EmailDataItem) {
+            final EmailDataItem email = (EmailDataItem) item;
+            final String address = email.getData();
             if (!TextUtils.isEmpty(address)) {
                 final Uri mailUri = Uri.fromParts(Constants.SCHEME_MAILTO, address, null);
                 mIntent = new Intent(Intent.ACTION_SENDTO, mailUri);
             }
 
-        } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType)) {
-            final String url = entryValues.getAsString(Website.URL);
+        } else if (item instanceof WebsiteDataItem) {
+            final WebsiteDataItem website = (WebsiteDataItem) item;
+            final String url = website.getUrl();
             if (!TextUtils.isEmpty(url)) {
                 WebAddress webAddress = new WebAddress(url);
                 mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
             }
 
-        } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType)) {
-            final boolean isEmail = Email.CONTENT_ITEM_TYPE.equals(
-                    entryValues.getAsString(Data.MIMETYPE));
-            if (isEmail || isProtocolValid(entryValues)) {
-                final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK :
-                        entryValues.getAsInteger(Im.PROTOCOL);
+        } else if (item instanceof ImDataItem) {
+            ImDataItem im = (ImDataItem) item;
+            final boolean isEmail = im.isCreatedFromEmail();
+            if (isEmail || im.isProtocolValid()) {
+                final int protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
 
                 if (isEmail) {
                     // Use Google Talk string when using Email, and clear data
@@ -176,8 +173,8 @@
                     mDataUri = null;
                 }
 
-                String host = entryValues.getAsString(Im.CUSTOM_PROTOCOL);
-                String data = entryValues.getAsString(isEmail ? Email.DATA : Im.DATA);
+                String host = im.getCustomProtocol();
+                String data = im.getData();
                 if (protocol != Im.PROTOCOL_CUSTOM) {
                     // Try bringing in a well-known host for specific protocols
                     host = ContactsUtils.lookupProviderNameFromId(protocol);
@@ -191,8 +188,7 @@
 
                     // If the address is also available for a video chat, we'll show the capability
                     // as a secondary action.
-                    final Integer chatCapabilityObj = entryValues.getAsInteger(Im.CHAT_CAPABILITY);
-                    final int chatCapability = chatCapabilityObj == null ? 0 : chatCapabilityObj;
+                    final int chatCapability = im.getChatCapability();
                     final boolean isVideoChatCapable =
                             (chatCapability & Im.CAPABILITY_HAS_CAMERA) != 0;
                     final boolean isAudioChatCapable =
@@ -210,9 +206,9 @@
                     }
                 }
             }
-        } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType)) {
-            final String postalAddress =
-                    entryValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
+        } else if (item instanceof StructuredPostalDataItem) {
+            StructuredPostalDataItem postal = (StructuredPostalDataItem) item;
+            final String postalAddress = postal.getFormattedAddress();
             if (!TextUtils.isEmpty(postalAddress)) {
                 mIntent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
             }
@@ -221,7 +217,7 @@
         if (mIntent == null) {
             // Otherwise fall back to default VIEW action
             mIntent = new Intent(Intent.ACTION_VIEW);
-            mIntent.setDataAndType(mDataUri, mimeType);
+            mIntent.setDataAndType(mDataUri, item.getMimeType());
         }
 
         mIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
@@ -236,19 +232,6 @@
         mPresence = presence;
     }
 
-    private boolean isProtocolValid(ContentValues entryValues) {
-        final String protocol = entryValues.getAsString(Im.PROTOCOL);
-        if (protocol == null) {
-            return false;
-        }
-        try {
-            Integer.valueOf(protocol);
-        } catch (NumberFormatException e) {
-            return false;
-        }
-        return true;
-    }
-
     @Override
     public CharSequence getSubtitle() {
         return mSubtitle;
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 4587f1d..25fb3f4 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -22,10 +22,7 @@
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.ActivityNotFoundException;
 import android.content.ContentUris;
-import android.content.ContentValues;
 import android.content.Context;
-import android.content.Entity;
-import android.content.Entity.NamedContentValues;
 import android.content.Intent;
 import android.content.Loader;
 import android.content.pm.PackageManager;
@@ -35,13 +32,11 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Im;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.SipAddress;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
 import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.QuickContact;
 import android.provider.ContactsContract.RawContacts;
 import android.support.v13.app.FragmentPagerAdapter;
@@ -62,10 +57,14 @@
 import android.widget.Toast;
 
 import com.android.contacts.Collapser;
-import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.DataKind;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.ContactLoader;
+import com.android.contacts.model.RawContact;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.DataKind;
+import com.android.contacts.model.dataitem.EmailDataItem;
+import com.android.contacts.model.dataitem.ImDataItem;
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.ImageViewDrawableSetter;
@@ -330,7 +329,7 @@
     /**
      * Handle the result from the ContactLoader
      */
-    private void bindData(ContactLoader.Result data) {
+    private void bindData(Contact data) {
         final ResolveCache cache = ResolveCache.getInstance(this);
         final Context context = this;
 
@@ -339,42 +338,29 @@
 
         mDefaultsMap.clear();
 
-        mStopWatch.lap("atm"); // AccountTypeManager initialization start
-        final AccountTypeManager accountTypes = AccountTypeManager.getInstance(
-                context.getApplicationContext());
-        mStopWatch.lap("fatm"); // AccountTypeManager initialization finished
+        mStopWatch.lap("sph"); // Start photo setting
 
         final ImageView photoView = (ImageView) mPhotoContainer.findViewById(R.id.photo);
         mPhotoSetter.setupContactPhoto(data, photoView);
 
         mStopWatch.lap("ph"); // Photo set
 
-        for (Entity entity : data.getEntities()) {
-            final ContentValues entityValues = entity.getEntityValues();
-            final String accountType = entityValues.getAsString(RawContacts.ACCOUNT_TYPE);
-            final String dataSet = entityValues.getAsString(RawContacts.DATA_SET);
-            for (NamedContentValues subValue : entity.getSubValues()) {
-                final ContentValues entryValues = subValue.values;
-                final String mimeType = entryValues.getAsString(Data.MIMETYPE);
+        for (RawContact rawContact : data.getRawContacts()) {
+            for (DataItem dataItem : rawContact.getDataItems()) {
+                final String mimeType = dataItem.getMimeType();
 
                 // Skip this data item if MIME-type excluded
                 if (isMimeExcluded(mimeType)) continue;
 
-                final long dataId = entryValues.getAsLong(Data._ID);
-                final Integer primary = entryValues.getAsInteger(Data.IS_PRIMARY);
-                final boolean isPrimary = primary != null && primary != 0;
-                final Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
-                final boolean isSuperPrimary = superPrimary != null && superPrimary != 0;
+                final long dataId = dataItem.getId();
+                final boolean isPrimary = dataItem.isPrimary();
+                final boolean isSuperPrimary = dataItem.isSuperPrimary();
 
-                final DataKind kind =
-                        accountTypes.getKindOrFallback(accountType, dataSet, mimeType);
-
-                if (kind != null) {
+                if (dataItem.getDataKind() != null) {
                     // Build an action for this data entry, find a mapping to a UI
                     // element, build its summary from the cursor, and collect it
                     // along with all others of this MIME-type.
-                    final Action action = new DataAction(context, mimeType, kind, dataId,
-                            entryValues);
+                    final Action action = new DataAction(context, dataItem);
                     final boolean wasAdded = considerAdd(action, cache, isSuperPrimary);
                     if (wasAdded) {
                         // Remember the default
@@ -386,12 +372,11 @@
 
                 // Handle Email rows with presence data as Im entry
                 final DataStatus status = data.getStatuses().get(dataId);
-                if (status != null && Email.CONTENT_ITEM_TYPE.equals(mimeType)) {
-                    final DataKind imKind = accountTypes.getKindOrFallback(accountType, dataSet,
-                            Im.CONTENT_ITEM_TYPE);
-                    if (imKind != null) {
-                        final DataAction action = new DataAction(context, Im.CONTENT_ITEM_TYPE,
-                                imKind, dataId, entryValues);
+                if (status != null && dataItem instanceof EmailDataItem) {
+                    final EmailDataItem email = (EmailDataItem) dataItem;
+                    final ImDataItem im = ImDataItem.createFromEmail(email);
+                    if (im.getDataKind() != null) {
+                        final DataAction action = new DataAction(context, im);
                         action.setPresence(status.getPresence());
                         considerAdd(action, cache, isSuperPrimary);
                     }
@@ -505,14 +490,14 @@
         listFragment.setListener(mListFragmentListener);
     }
 
-    private LoaderCallbacks<ContactLoader.Result> mLoaderCallbacks =
-            new LoaderCallbacks<ContactLoader.Result>() {
+    private LoaderCallbacks<Contact> mLoaderCallbacks =
+            new LoaderCallbacks<Contact>() {
         @Override
-        public void onLoaderReset(Loader<ContactLoader.Result> loader) {
+        public void onLoaderReset(Loader<Contact> loader) {
         }
 
         @Override
-        public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
+        public void onLoadFinished(Loader<Contact> loader, Contact data) {
             mStopWatch.lap("lf"); // onLoadFinished
             if (isFinishing()) {
                 close(false);
@@ -558,7 +543,7 @@
         }
 
         @Override
-        public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
+        public Loader<Contact> onCreateLoader(int id, Bundle args) {
             if (mLookupUri == null) {
                 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
             }
diff --git a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
index b80169b..03517d4 100644
--- a/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
+++ b/src/com/android/contacts/socialwidget/SocialWidgetProvider.java
@@ -37,10 +37,11 @@
 import android.view.View;
 import android.widget.RemoteViews;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.ContactLoader;
+import com.android.contacts.model.account.AccountType;
 import com.android.contacts.quickcontact.QuickContactBroadcastReceiver;
 import com.android.contacts.util.ContactBadgeUtil;
 import com.android.contacts.util.HtmlUtils;
@@ -115,10 +116,10 @@
         final ContactLoader contactLoader = new ContactLoader(context, contactUri, false, true,
                 false, true);
         contactLoader.registerListener(0,
-                new ContactLoader.OnLoadCompleteListener<ContactLoader.Result>() {
+                new ContactLoader.OnLoadCompleteListener<Contact>() {
                     @Override
-                    public void onLoadComplete(Loader<ContactLoader.Result> loader,
-                            ContactLoader.Result contactData) {
+                    public void onLoadComplete(Loader<Contact> loader,
+                            Contact contactData) {
                         bindRemoteViews(context, widgetId, appWidgetManager, contactData);
                     }
                 });
@@ -127,7 +128,7 @@
     }
 
     private static void bindRemoteViews(final Context context, final int widgetId,
-            final AppWidgetManager widgetManager, ContactLoader.Result contactData) {
+            final AppWidgetManager widgetManager, Contact contactData) {
         Log.d(TAG, "Loaded " + contactData.getLookupKey()
                 + " for widget with id=" + widgetId);
         final RemoteViews views = new RemoteViews(context.getPackageName(),
diff --git a/src/com/android/contacts/util/AccountPromptUtils.java b/src/com/android/contacts/util/AccountPromptUtils.java
index 8c8c305..cdefda0 100644
--- a/src/com/android/contacts/util/AccountPromptUtils.java
+++ b/src/com/android/contacts/util/AccountPromptUtils.java
@@ -30,7 +30,7 @@
 import android.util.Log;
 
 import com.android.contacts.R;
-import com.android.contacts.model.GoogleAccountType;
+import com.android.contacts.model.account.GoogleAccountType;
 
 import java.io.IOException;
 
diff --git a/src/com/android/contacts/util/AccountSelectionUtil.java b/src/com/android/contacts/util/AccountSelectionUtil.java
index 312dd16..d83cb41 100644
--- a/src/com/android/contacts/util/AccountSelectionUtil.java
+++ b/src/com/android/contacts/util/AccountSelectionUtil.java
@@ -31,9 +31,9 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 
 import java.util.List;
 
diff --git a/src/com/android/contacts/util/AccountsListAdapter.java b/src/com/android/contacts/util/AccountsListAdapter.java
index 5f17cee..4355cba 100644
--- a/src/com/android/contacts/util/AccountsListAdapter.java
+++ b/src/com/android/contacts/util/AccountsListAdapter.java
@@ -26,9 +26,9 @@
 import android.widget.TextView;
 
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/src/com/android/contacts/util/ImageViewDrawableSetter.java b/src/com/android/contacts/util/ImageViewDrawableSetter.java
index 4c7869b..b231572 100644
--- a/src/com/android/contacts/util/ImageViewDrawableSetter.java
+++ b/src/com/android/contacts/util/ImageViewDrawableSetter.java
@@ -26,8 +26,8 @@
 import android.util.Log;
 import android.widget.ImageView;
 
-import com.android.contacts.ContactLoader.Result;
 import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.model.Contact;
 
 import java.util.Arrays;
 
@@ -49,7 +49,7 @@
         mTarget = target;
     }
 
-    public void setupContactPhoto(Result contactData, ImageView photoView) {
+    public void setupContactPhoto(Contact contactData, ImageView photoView) {
         setTarget(photoView);
         setCompressedImage(contactData.getPhotoBinaryData());
     }
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 48d6e83..a9566ef 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -49,7 +49,7 @@
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.AccountSelectionUtil;
 import com.android.vcard.VCardEntryCounter;
 import com.android.vcard.VCardParser;
diff --git a/src/com/android/contacts/vcard/NfcImportVCardActivity.java b/src/com/android/contacts/vcard/NfcImportVCardActivity.java
index f427358..035be60 100644
--- a/src/com/android/contacts/vcard/NfcImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/NfcImportVCardActivity.java
@@ -33,7 +33,7 @@
 
 import com.android.contacts.R;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.vcard.VCardEntry;
 import com.android.vcard.VCardEntryCounter;
 import com.android.vcard.VCardParser;
diff --git a/src/com/android/contacts/vcard/SelectAccountActivity.java b/src/com/android/contacts/vcard/SelectAccountActivity.java
index 1226385..0e9c5a8 100644
--- a/src/com/android/contacts/vcard/SelectAccountActivity.java
+++ b/src/com/android/contacts/vcard/SelectAccountActivity.java
@@ -24,7 +24,7 @@
 import com.android.contacts.ContactsActivity;
 import com.android.contacts.R;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.util.AccountSelectionUtil;
 
 import java.util.List;
diff --git a/tests/src/com/android/contacts/EntityDeltaListTests.java b/tests/src/com/android/contacts/RawContactDeltaListTests.java
similarity index 70%
rename from tests/src/com/android/contacts/EntityDeltaListTests.java
rename to tests/src/com/android/contacts/RawContactDeltaListTests.java
index fd2fcb1..165e689 100644
--- a/tests/src/com/android/contacts/EntityDeltaListTests.java
+++ b/tests/src/com/android/contacts/RawContactDeltaListTests.java
@@ -23,7 +23,7 @@
 
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
-import android.content.Entity;
+import android.content.Context;
 import android.net.Uri;
 import android.provider.BaseColumns;
 import android.provider.ContactsContract.AggregationExceptions;
@@ -34,24 +34,25 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import com.android.contacts.EntityModifierTests.MockContactsSource;
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
+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.RawContactDeltaList;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
 import com.google.common.collect.Lists;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 
 /**
- * Tests for {@link EntityDeltaList} which focus on "diff" operations that should
+ * Tests for {@link RawContactDeltaList} which focus on "diff" operations that should
  * create {@link AggregationExceptions} in certain cases.
  */
 @LargeTest
-public class EntityDeltaListTests extends AndroidTestCase {
-    public static final String TAG = "EntityDeltaListTests";
+public class RawContactDeltaListTests extends AndroidTestCase {
+    public static final String TAG = RawContactDeltaListTests.class.getSimpleName();
 
     private static final long CONTACT_FIRST = 1;
     private static final long CONTACT_SECOND = 2;
@@ -71,7 +72,7 @@
     public static final String TEST_PHONE = "555-1212";
     public static final String TEST_ACCOUNT = "org.example.test";
 
-    public EntityDeltaListTests() {
+    public RawContactDeltaListTests() {
         super();
     }
 
@@ -95,47 +96,47 @@
         return (ContentValues) field.get(operation);
     }
 
-    static EntityDelta getUpdate(long rawContactId) {
-        final Entity before = EntityDeltaTests.getEntity(rawContactId,
-                EntityDeltaTests.TEST_PHONE_ID);
-        return EntityDelta.fromBefore(before);
+    static RawContactDelta getUpdate(Context context, long rawContactId) {
+        final RawContact before = RawContactDeltaTests.getRawContact(context, rawContactId,
+                RawContactDeltaTests.TEST_PHONE_ID);
+        return RawContactDelta.fromBefore(before);
     }
 
-    static EntityDelta getInsert() {
+    static RawContactDelta getInsert() {
         final ContentValues after = new ContentValues();
-        after.put(RawContacts.ACCOUNT_NAME, EntityDeltaTests.TEST_ACCOUNT_NAME);
+        after.put(RawContacts.ACCOUNT_NAME, RawContactDeltaTests.TEST_ACCOUNT_NAME);
         after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
 
         final ValuesDelta values = ValuesDelta.fromAfter(after);
-        return new EntityDelta(values);
+        return new RawContactDelta(values);
     }
 
-    static EntityDeltaList buildSet(EntityDelta... deltas) {
-        final EntityDeltaList set = EntityDeltaList.fromSingle(deltas[0]);
+    static RawContactDeltaList buildSet(RawContactDelta... deltas) {
+        final RawContactDeltaList set = RawContactDeltaList.fromSingle(deltas[0]);
         for (int i = 1; i < deltas.length; i++) {
             set.add(deltas[i]);
         }
         return set;
     }
 
-    static EntityDelta buildBeforeEntity(long rawContactId, long version,
+    static RawContactDelta buildBeforeEntity(Context context, long rawContactId, long version,
             ContentValues... entries) {
         // Build an existing contact read from database
         final ContentValues contact = new ContentValues();
         contact.put(RawContacts.VERSION, version);
         contact.put(RawContacts._ID, rawContactId);
-        final Entity before = new Entity(contact);
+        final RawContact before = new RawContact(context, contact);
         for (ContentValues entry : entries) {
-            before.addSubValue(Data.CONTENT_URI, entry);
+            before.addDataItemValues(entry);
         }
-        return EntityDelta.fromBefore(before);
+        return RawContactDelta.fromBefore(before);
     }
 
-    static EntityDelta buildAfterEntity(ContentValues... entries) {
+    static RawContactDelta buildAfterEntity(ContentValues... entries) {
         // Build an existing contact read from database
         final ContentValues contact = new ContentValues();
         contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT);
-        final EntityDelta after = new EntityDelta(ValuesDelta.fromAfter(contact));
+        final RawContactDelta after = new RawContactDelta(ValuesDelta.fromAfter(contact));
         for (ContentValues entry : entries) {
             after.addEntry(ValuesDelta.fromAfter(entry));
         }
@@ -164,24 +165,24 @@
         return values;
     }
 
-    static void insertPhone(EntityDeltaList set, long rawContactId, ContentValues values) {
-        final EntityDelta match = set.getByRawContactId(rawContactId);
+    static void insertPhone(RawContactDeltaList set, long rawContactId, ContentValues values) {
+        final RawContactDelta match = set.getByRawContactId(rawContactId);
         match.addEntry(ValuesDelta.fromAfter(values));
     }
 
-    static ValuesDelta getPhone(EntityDeltaList set, long rawContactId, long dataId) {
-        final EntityDelta match = set.getByRawContactId(rawContactId);
+    static ValuesDelta getPhone(RawContactDeltaList set, long rawContactId, long dataId) {
+        final RawContactDelta match = set.getByRawContactId(rawContactId);
         return match.getEntry(dataId);
     }
 
-    static void assertDiffPattern(EntityDelta delta, ContentProviderOperation... pattern) {
+    static void assertDiffPattern(RawContactDelta delta, ContentProviderOperation... pattern) {
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
         delta.buildAssert(diff);
         delta.buildDiff(diff);
         assertDiffPattern(diff, pattern);
     }
 
-    static void assertDiffPattern(EntityDeltaList set, ContentProviderOperation... pattern) {
+    static void assertDiffPattern(RawContactDeltaList set, ContentProviderOperation... pattern) {
         assertDiffPattern(set.buildDiff(), pattern);
     }
 
@@ -281,7 +282,7 @@
         return null;
     }
 
-    static Long getVersion(EntityDeltaList set, Long rawContactId) {
+    static Long getVersion(RawContactDeltaList set, Long rawContactId) {
         return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION);
     }
 
@@ -301,8 +302,8 @@
     }
 
     public void testInsert() {
-        final EntityDelta insert = getInsert();
-        final EntityDeltaList set = buildSet(insert);
+        final RawContactDelta insert = getInsert();
+        final RawContactDeltaList set = buildSet(insert);
 
         // Inserting single shouldn't create rules
         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -311,9 +312,9 @@
     }
 
     public void testUpdateUpdate() {
-        final EntityDelta updateFirst = getUpdate(CONTACT_FIRST);
-        final EntityDelta updateSecond = getUpdate(CONTACT_SECOND);
-        final EntityDeltaList set = buildSet(updateFirst, updateSecond);
+        final RawContactDelta updateFirst = getUpdate(mContext, CONTACT_FIRST);
+        final RawContactDelta updateSecond = getUpdate(mContext, CONTACT_SECOND);
+        final RawContactDeltaList set = buildSet(updateFirst, updateSecond);
 
         // Updating two existing shouldn't create rules
         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -322,9 +323,9 @@
     }
 
     public void testUpdateInsert() {
-        final EntityDelta update = getUpdate(CONTACT_FIRST);
-        final EntityDelta insert = getInsert();
-        final EntityDeltaList set = buildSet(update, insert);
+        final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST);
+        final RawContactDelta insert = getInsert();
+        final RawContactDeltaList set = buildSet(update, insert);
 
         // New insert should only create one rule
         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -333,10 +334,10 @@
     }
 
     public void testInsertUpdateInsert() {
-        final EntityDelta insertFirst = getInsert();
-        final EntityDelta update = getUpdate(CONTACT_FIRST);
-        final EntityDelta insertSecond = getInsert();
-        final EntityDeltaList set = buildSet(insertFirst, update, insertSecond);
+        final RawContactDelta insertFirst = getInsert();
+        final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST);
+        final RawContactDelta insertSecond = getInsert();
+        final RawContactDeltaList set = buildSet(insertFirst, update, insertSecond);
 
         // Two inserts should create two rules to bind against single existing
         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -345,10 +346,10 @@
     }
 
     public void testInsertInsertInsert() {
-        final EntityDelta insertFirst = getInsert();
-        final EntityDelta insertSecond = getInsert();
-        final EntityDelta insertThird = getInsert();
-        final EntityDeltaList set = buildSet(insertFirst, insertSecond, insertThird);
+        final RawContactDelta insertFirst = getInsert();
+        final RawContactDelta insertSecond = getInsert();
+        final RawContactDelta insertThird = getInsert();
+        final RawContactDeltaList set = buildSet(insertFirst, insertSecond, insertThird);
 
         // Three new inserts should create only two binding rules
         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
@@ -357,21 +358,21 @@
     }
 
     public void testMergeDataRemoteInsert() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
 
         // Merge in second version, verify they match
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertEquals("Unexpected change when merging", second, merged);
     }
 
     public void testMergeDataLocalUpdateRemoteInsert() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
 
         // Change the local number to trigger update
         final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
@@ -384,7 +385,7 @@
                 buildUpdateAggregationDefault());
 
         // Merge in the second version, verify diff matches
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged,
                 buildAssertVersion(VER_SECOND),
                 buildUpdateAggregationSuspended(),
@@ -393,10 +394,10 @@
     }
 
     public void testMergeDataLocalUpdateRemoteDelete() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_GREEN)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_GREEN)));
 
         // Change the local number to trigger update
         final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
@@ -410,7 +411,7 @@
 
         // Merge in the second version, verify that our update changed to
         // insert, since RED was deleted on remote side
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged,
                 buildAssertVersion(VER_SECOND),
                 buildUpdateAggregationSuspended(),
@@ -419,10 +420,10 @@
     }
 
     public void testMergeDataLocalDeleteRemoteUpdate() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_RED, TEST_PHONE)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_RED, TEST_PHONE)));
 
         // Delete phone locally
         final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
@@ -435,7 +436,7 @@
                 buildUpdateAggregationDefault());
 
         // Merge in the second version, verify that our delete remains
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged,
                 buildAssertVersion(VER_SECOND),
                 buildUpdateAggregationSuspended(),
@@ -444,10 +445,10 @@
     }
 
     public void testMergeDataLocalInsertRemoteInsert() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
 
         // Insert new phone locally
         final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE));
@@ -459,7 +460,7 @@
                 buildUpdateAggregationDefault());
 
         // Merge in the second version, verify that our insert remains
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged,
                 buildAssertVersion(VER_SECOND),
                 buildUpdateAggregationSuspended(),
@@ -468,15 +469,15 @@
     }
 
     public void testMergeRawContactLocalInsertRemoteInsert() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_RED)), buildBeforeEntity(CONTACT_MARY, VER_SECOND,
-                buildPhone(PHONE_RED)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_RED)), buildBeforeEntity(mContext, CONTACT_MARY,
+                        VER_SECOND, buildPhone(PHONE_RED)));
 
         // Add new contact locally, should remain insert
         final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE);
-        final EntityDelta joeContact = buildAfterEntity(joePhoneInsert);
+        final RawContactDelta joeContact = buildAfterEntity(joePhoneInsert);
         final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues();
         joeContactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
         first.add(joeContact);
@@ -488,7 +489,7 @@
                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
 
         // Merge in the second version, verify that our insert remains
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged,
                 buildAssertVersion(VER_SECOND),
                 buildAssertVersion(VER_SECOND),
@@ -499,11 +500,11 @@
     }
 
     public void testMergeRawContactLocalDeleteRemoteDelete() {
-        final EntityDeltaList first = buildSet(
-                buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
-                buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(
-                buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
+        final RawContactDeltaList first = buildSet(
+                buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
+                buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(
+                buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
 
         // Remove contact locally
         first.getByRawContactId(CONTACT_MARY).markDeleted();
@@ -513,16 +514,16 @@
                 buildDelete(RawContacts.CONTENT_URI));
 
         // Merge in the second version, verify that our delete isn't needed
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged);
     }
 
     public void testMergeRawContactLocalUpdateRemoteDelete() {
-        final EntityDeltaList first = buildSet(
-                buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
-                buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(
-                buildBeforeEntity(CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
+        final RawContactDeltaList first = buildSet(
+                buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
+                buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(
+                buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
 
         // Perform local update
         final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED);
@@ -540,7 +541,7 @@
         contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
 
         // Merge and verify that update turned into insert
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged,
                 buildAssertVersion(VER_SECOND),
                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
@@ -550,28 +551,28 @@
     }
 
     public void testMergeUsesNewVersion() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildPhone(PHONE_RED)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildPhone(PHONE_RED)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildPhone(PHONE_RED)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildPhone(PHONE_RED)));
 
         assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB));
         assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB));
 
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB));
     }
 
     public void testMergeAfterEnsureAndTrim() {
-        final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST,
-                buildEmail(EMAIL_YELLOW)));
-        final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND,
-                buildEmail(EMAIL_YELLOW)));
+        final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_FIRST, buildEmail(EMAIL_YELLOW)));
+        final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
+                VER_SECOND, buildEmail(EMAIL_YELLOW)));
 
         // Ensure we have at least one phone
         final AccountType source = getAccountType();
-        final EntityDelta bobContact = first.getByRawContactId(CONTACT_BOB);
-        EntityModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE);
+        final RawContactDelta bobContact = first.getByRawContactId(CONTACT_BOB);
+        RawContactModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE);
         final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true);
 
         // Make sure the update would insert a row
@@ -582,11 +583,11 @@
                 buildUpdateAggregationDefault());
 
         // Trim values and ensure that we don't insert things
-        EntityModifier.trimEmpty(bobContact, source);
+        RawContactModifier.trimEmpty(bobContact, source);
         assertDiffPattern(first);
 
         // Now re-parent the change, which should remain no-op
-        final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first);
+        final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
         assertDiffPattern(merged);
     }
 }
diff --git a/tests/src/com/android/contacts/EntityDeltaTests.java b/tests/src/com/android/contacts/RawContactDeltaTests.java
similarity index 82%
rename from tests/src/com/android/contacts/EntityDeltaTests.java
rename to tests/src/com/android/contacts/RawContactDeltaTests.java
index d873748..80e4c20 100644
--- a/tests/src/com/android/contacts/EntityDeltaTests.java
+++ b/tests/src/com/android/contacts/RawContactDeltaTests.java
@@ -24,7 +24,7 @@
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderOperation.Builder;
 import android.content.ContentValues;
-import android.content.Entity;
+import android.content.Context;
 import android.os.Parcel;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Data;
@@ -32,19 +32,20 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.model.RawContact;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
 import com.google.common.collect.Lists;
 
 import java.util.ArrayList;
 
 /**
- * Tests for {@link EntityDelta} and {@link ValuesDelta}. These tests
+ * Tests for {@link RawContactDelta} and {@link ValuesDelta}. These tests
  * focus on passing changes across {@link Parcel}, and verifying that they
  * correctly build expected "diff" operations.
  */
 @LargeTest
-public class EntityDeltaTests extends AndroidTestCase {
+public class RawContactDeltaTests extends AndroidTestCase {
     public static final String TAG = "EntityDeltaTests";
 
     public static final long TEST_CONTACT_ID = 12;
@@ -55,7 +56,7 @@
 
     public static final String TEST_ACCOUNT_NAME = "TEST";
 
-    public EntityDeltaTests() {
+    public RawContactDeltaTests() {
         super();
     }
 
@@ -64,7 +65,7 @@
         mContext = getContext();
     }
 
-    public static Entity getEntity(long contactId, long phoneId) {
+    public static RawContact getRawContact(Context context, long contactId, long phoneId) {
         // Build an existing contact read from database
         final ContentValues contact = new ContentValues();
         contact.put(RawContacts.VERSION, 43);
@@ -76,31 +77,31 @@
         phone.put(Phone.NUMBER, TEST_PHONE_NUMBER_1);
         phone.put(Phone.TYPE, Phone.TYPE_HOME);
 
-        final Entity before = new Entity(contact);
-        before.addSubValue(Data.CONTENT_URI, phone);
+        final RawContact before = new RawContact(context, contact);
+        before.addDataItemValues(phone);
         return before;
     }
 
     /**
-     * Test that {@link EntityDelta#mergeAfter(EntityDelta)} correctly passes
+     * Test that {@link RawContactDelta#mergeAfter(RawContactDelta)} correctly passes
      * any changes through the {@link Parcel} object. This enforces that
-     * {@link EntityDelta} should be identical when serialized against the same
-     * "before" {@link Entity}.
+     * {@link RawContactDelta} should be identical when serialized against the same
+     * "before" {@link RawContact}.
      */
     public void testParcelChangesNone() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
-        final EntityDelta dest = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
+        final RawContactDelta dest = RawContactDelta.fromBefore(before);
 
         // Merge modified values and assert they match
-        final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+        final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source);
         assertEquals("Unexpected change when merging", source, merged);
     }
 
     public void testParcelChangesInsert() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
-        final EntityDelta dest = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
+        final RawContactDelta dest = RawContactDelta.fromBefore(before);
 
         // Add a new row and pass across parcel, should be same
         final ContentValues phone = new ContentValues();
@@ -110,35 +111,35 @@
         source.addEntry(ValuesDelta.fromAfter(phone));
 
         // Merge modified values and assert they match
-        final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+        final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source);
         assertEquals("Unexpected change when merging", source, merged);
     }
 
     public void testParcelChangesUpdate() {
         // Update existing row and pass across parcel, should be same
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
-        final EntityDelta dest = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
+        final RawContactDelta dest = RawContactDelta.fromBefore(before);
 
         final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
         child.put(Phone.NUMBER, TEST_PHONE_NUMBER_2);
 
         // Merge modified values and assert they match
-        final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+        final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source);
         assertEquals("Unexpected change when merging", source, merged);
     }
 
     public void testParcelChangesDelete() {
         // Delete a row and pass across parcel, should be same
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
-        final EntityDelta dest = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
+        final RawContactDelta dest = RawContactDelta.fromBefore(before);
 
         final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
         child.markDeleted();
 
         // Merge modified values and assert they match
-        final EntityDelta merged = EntityDelta.mergeAfter(dest, source);
+        final RawContactDelta merged = RawContactDelta.mergeAfter(dest, source);
         assertEquals("Unexpected change when merging", source, merged);
     }
 
@@ -200,13 +201,13 @@
     }
 
     /**
-     * Test that {@link EntityDelta#buildDiff(ArrayList)} is correctly built for
+     * Test that {@link RawContactDelta#buildDiff(ArrayList)} is correctly built for
      * insert, update, and delete cases. This only tests a subset of possible
      * {@link Data} row changes.
      */
     public void testEntityDiffNone() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
 
         // Assert that writing unchanged produces few operations
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -216,8 +217,8 @@
     }
 
     public void testEntityDiffNoneInsert() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
 
         // Insert a new phone number
         final ContentValues phone = new ContentValues();
@@ -253,8 +254,8 @@
     }
 
     public void testEntityDiffUpdateInsert() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
 
         // Update parent contact values
         source.getValues().put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
@@ -298,8 +299,8 @@
     }
 
     public void testEntityDiffNoneUpdate() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
 
         // Update existing phone number
         final ValuesDelta child = source.getEntry(TEST_PHONE_ID);
@@ -332,8 +333,8 @@
     }
 
     public void testEntityDiffDelete() {
-        final Entity before = getEntity(TEST_CONTACT_ID, TEST_PHONE_ID);
-        final EntityDelta source = EntityDelta.fromBefore(before);
+        final RawContact before = getRawContact(mContext, TEST_CONTACT_ID, TEST_PHONE_ID);
+        final RawContactDelta source = RawContactDelta.fromBefore(before);
 
         // Delete entire entity
         source.getValues().markDeleted();
@@ -361,7 +362,7 @@
         after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
 
         final ValuesDelta values = ValuesDelta.fromAfter(after);
-        final EntityDelta source = new EntityDelta(values);
+        final RawContactDelta source = new RawContactDelta(values);
 
         // Assert two operations: delete Contact and enforce version
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -382,7 +383,7 @@
         after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
 
         final ValuesDelta values = ValuesDelta.fromAfter(after);
-        final EntityDelta source = new EntityDelta(values);
+        final RawContactDelta source = new RawContactDelta(values);
 
         // Insert a new phone number
         final ContentValues phone = new ContentValues();
diff --git a/tests/src/com/android/contacts/EntityModifierTests.java b/tests/src/com/android/contacts/RawContactModifierTests.java
similarity index 80%
rename from tests/src/com/android/contacts/EntityModifierTests.java
rename to tests/src/com/android/contacts/RawContactModifierTests.java
index 6a68baf..9254a7e 100644
--- a/tests/src/com/android/contacts/EntityModifierTests.java
+++ b/tests/src/com/android/contacts/RawContactModifierTests.java
@@ -22,7 +22,6 @@
 
 import android.content.ContentProviderOperation;
 import android.content.ContentValues;
-import android.content.Entity;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract;
@@ -39,16 +38,17 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountType.EditType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.DataKind;
-import com.android.contacts.model.EntityDelta;
-import com.android.contacts.model.EntityDelta.ValuesDelta;
-import com.android.contacts.model.EntityDeltaList;
-import com.android.contacts.model.EntityModifier;
-import com.android.contacts.model.ExchangeAccountType;
-import com.android.contacts.model.GoogleAccountType;
+import com.android.contacts.model.RawContact;
+import com.android.contacts.model.RawContactDelta;
+import com.android.contacts.model.RawContactDelta.ValuesDelta;
+import com.android.contacts.model.RawContactDeltaList;
+import com.android.contacts.model.RawContactModifier;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountType.EditType;
+import com.android.contacts.model.account.ExchangeAccountType;
+import com.android.contacts.model.account.GoogleAccountType;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.tests.mocks.ContactsMockContext;
 import com.android.contacts.tests.mocks.MockAccountTypeManager;
 import com.android.contacts.tests.mocks.MockContentProvider;
@@ -58,11 +58,11 @@
 import java.util.List;
 
 /**
- * Tests for {@link EntityModifier} to verify that {@link AccountType}
+ * Tests for {@link RawContactModifier} to verify that {@link AccountType}
  * constraints are being enforced correctly.
  */
 @LargeTest
-public class EntityModifierTests extends AndroidTestCase {
+public class RawContactModifierTests extends AndroidTestCase {
     public static final String TAG = "EntityModifierTests";
 
     public static final long VER_FIRST = 100;
@@ -168,9 +168,9 @@
     }
 
     /**
-     * Build an {@link Entity} with the requested set of phone numbers.
+     * Build an {@link RawContact} with the requested set of phone numbers.
      */
-    protected EntityDelta getEntity(Long existingId, ContentValues... entries) {
+    protected RawContactDelta getRawContact(Long existingId, ContentValues... entries) {
         final ContentValues contact = new ContentValues();
         if (existingId != null) {
             contact.put(RawContacts._ID, existingId);
@@ -178,11 +178,11 @@
         contact.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
         contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
 
-        final Entity before = new Entity(contact);
+        final RawContact before = new RawContact(mContext, contact);
         for (ContentValues values : entries) {
-            before.addSubValue(Data.CONTENT_URI, values);
+            before.addDataItemValues(values);
         }
-        return EntityDelta.fromBefore(before);
+        return RawContactDelta.fromBefore(before);
     }
 
     /**
@@ -201,116 +201,116 @@
 
     /**
      * Insert various rows to test
-     * {@link EntityModifier#getValidTypes(EntityDelta, DataKind, EditType)}
+     * {@link RawContactModifier#getValidTypes(RawContactDelta, DataKind, EditType)}
      */
     public void testValidTypes() {
         // Build a source and pull specific types
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
-        final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
-        final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
+        final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
 
         List<EditType> validTypes;
 
         // Add first home, first work
-        final EntityDelta state = getEntity(TEST_ID);
-        EntityModifier.insertChild(state, kindPhone, typeHome);
-        EntityModifier.insertChild(state, kindPhone, typeWork);
+        final RawContactDelta state = getRawContact(TEST_ID);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
+        RawContactModifier.insertChild(state, kindPhone, typeWork);
 
         // Expecting home, other
-        validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+        validTypes = RawContactModifier.getValidTypes(state, kindPhone, null);
         assertContains(validTypes, typeHome);
         assertNotContains(validTypes, typeWork);
         assertContains(validTypes, typeOther);
 
         // Add second home
-        EntityModifier.insertChild(state, kindPhone, typeHome);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
 
         // Expecting other
-        validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+        validTypes = RawContactModifier.getValidTypes(state, kindPhone, null);
         assertNotContains(validTypes, typeHome);
         assertNotContains(validTypes, typeWork);
         assertContains(validTypes, typeOther);
 
         // Add third and fourth home (invalid, but possible)
-        EntityModifier.insertChild(state, kindPhone, typeHome);
-        EntityModifier.insertChild(state, kindPhone, typeHome);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
 
         // Expecting none
-        validTypes = EntityModifier.getValidTypes(state, kindPhone, null);
+        validTypes = RawContactModifier.getValidTypes(state, kindPhone, null);
         assertNotContains(validTypes, typeHome);
         assertNotContains(validTypes, typeWork);
         assertNotContains(validTypes, typeOther);
     }
 
     /**
-     * Test {@link EntityModifier#canInsert(EntityDelta, DataKind)} by
+     * Test {@link RawContactModifier#canInsert(RawContactDelta, DataKind)} by
      * inserting various rows.
      */
     public void testCanInsert() {
         // Build a source and pull specific types
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
-        final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
-        final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
+        final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
 
         // Add first home, first work
-        final EntityDelta state = getEntity(TEST_ID);
-        EntityModifier.insertChild(state, kindPhone, typeHome);
-        EntityModifier.insertChild(state, kindPhone, typeWork);
-        assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));
+        final RawContactDelta state = getRawContact(TEST_ID);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
+        RawContactModifier.insertChild(state, kindPhone, typeWork);
+        assertTrue("Unable to insert", RawContactModifier.canInsert(state, kindPhone));
 
         // Add two other, which puts us just under "5" overall limit
-        EntityModifier.insertChild(state, kindPhone, typeOther);
-        EntityModifier.insertChild(state, kindPhone, typeOther);
-        assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone));
+        RawContactModifier.insertChild(state, kindPhone, typeOther);
+        RawContactModifier.insertChild(state, kindPhone, typeOther);
+        assertTrue("Unable to insert", RawContactModifier.canInsert(state, kindPhone));
 
         // Add second home, which should push to snug limit
-        EntityModifier.insertChild(state, kindPhone, typeHome);
-        assertFalse("Able to insert", EntityModifier.canInsert(state, kindPhone));
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
+        assertFalse("Able to insert", RawContactModifier.canInsert(state, kindPhone));
     }
 
     /**
      * Test
-     * {@link EntityModifier#getBestValidType(EntityDelta, DataKind, boolean, int)}
+     * {@link RawContactModifier#getBestValidType(RawContactDelta, DataKind, boolean, int)}
      * by asserting expected best options in various states.
      */
     public void testBestValidType() {
         // Build a source and pull specific types
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
-        final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK);
-        final EditType typeFaxWork = EntityModifier.getType(kindPhone, Phone.TYPE_FAX_WORK);
-        final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
+        final EditType typeFaxWork = RawContactModifier.getType(kindPhone, Phone.TYPE_FAX_WORK);
+        final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
 
         EditType suggested;
 
         // Default suggestion should be home
-        final EntityDelta state = getEntity(TEST_ID);
-        suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+        final RawContactDelta state = getRawContact(TEST_ID);
+        suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
         assertEquals("Unexpected suggestion", typeHome, suggested);
 
         // Add first home, should now suggest work
-        EntityModifier.insertChild(state, kindPhone, typeHome);
-        suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
+        suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
         assertEquals("Unexpected suggestion", typeWork, suggested);
 
         // Add work fax, should still suggest work
-        EntityModifier.insertChild(state, kindPhone, typeFaxWork);
-        suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+        RawContactModifier.insertChild(state, kindPhone, typeFaxWork);
+        suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
         assertEquals("Unexpected suggestion", typeWork, suggested);
 
         // Add other, should still suggest work
-        EntityModifier.insertChild(state, kindPhone, typeOther);
-        suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+        RawContactModifier.insertChild(state, kindPhone, typeOther);
+        suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
         assertEquals("Unexpected suggestion", typeWork, suggested);
 
         // Add work, now should suggest other
-        EntityModifier.insertChild(state, kindPhone, typeWork);
-        suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
+        RawContactModifier.insertChild(state, kindPhone, typeWork);
+        suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
         assertEquals("Unexpected suggestion", typeOther, suggested);
     }
 
@@ -322,34 +322,34 @@
         final ContentValues after = new ContentValues();
         final ValuesDelta values = ValuesDelta.fromAfter(after);
 
-        assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone));
+        assertTrue("Expected empty", RawContactModifier.isEmpty(values, kindPhone));
     }
 
     public void testIsEmptyDirectFields() {
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Test row that has type values, but core fields are empty
-        final EntityDelta state = getEntity(TEST_ID);
-        final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
+        final RawContactDelta state = getRawContact(TEST_ID);
+        final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome);
 
-        assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone));
+        assertTrue("Expected empty", RawContactModifier.isEmpty(values, kindPhone));
 
         // Insert some data to trigger non-empty state
         values.put(Phone.NUMBER, TEST_PHONE);
 
-        assertFalse("Expected non-empty", EntityModifier.isEmpty(values, kindPhone));
+        assertFalse("Expected non-empty", RawContactModifier.isEmpty(values, kindPhone));
     }
 
     public void testTrimEmptySingle() {
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Test row that has type values, but core fields are empty
-        final EntityDelta state = getEntity(TEST_ID);
-        EntityModifier.insertChild(state, kindPhone, typeHome);
+        final RawContactDelta state = getRawContact(TEST_ID);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
 
         // Build diff, expecting insert for data row and update enforcement
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -372,7 +372,7 @@
         }
 
         // Trim empty rows and try again, expecting delete of overall contact
-        EntityModifier.trimEmpty(state, source);
+        RawContactModifier.trimEmpty(state, source);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 1, diff.size());
@@ -386,63 +386,65 @@
     public void testTrimEmptySpaces() {
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Test row that has type values, but values are spaces
-        final EntityDelta state = EntityDeltaListTests.buildBeforeEntity(TEST_ID, VER_FIRST);
-        final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
+        final RawContactDelta state = RawContactDeltaListTests.buildBeforeEntity(mContext, TEST_ID,
+                VER_FIRST);
+        final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome);
         values.put(Phone.NUMBER, "   ");
 
         // Build diff, expecting insert for data row and update enforcement
-        EntityDeltaListTests.assertDiffPattern(state,
-                EntityDeltaListTests.buildAssertVersion(VER_FIRST),
-                EntityDeltaListTests.buildUpdateAggregationSuspended(),
-                EntityDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
-                        EntityDeltaListTests.buildDataInsert(values, TEST_ID)),
-                EntityDeltaListTests.buildUpdateAggregationDefault());
+        RawContactDeltaListTests.assertDiffPattern(state,
+                RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
+                RawContactDeltaListTests.buildUpdateAggregationSuspended(),
+                RawContactDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
+                        RawContactDeltaListTests.buildDataInsert(values, TEST_ID)),
+                RawContactDeltaListTests.buildUpdateAggregationDefault());
 
         // Trim empty rows and try again, expecting delete of overall contact
-        EntityModifier.trimEmpty(state, source);
-        EntityDeltaListTests.assertDiffPattern(state,
-                EntityDeltaListTests.buildAssertVersion(VER_FIRST),
-                EntityDeltaListTests.buildDelete(RawContacts.CONTENT_URI));
+        RawContactModifier.trimEmpty(state, source);
+        RawContactDeltaListTests.assertDiffPattern(state,
+                RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
+                RawContactDeltaListTests.buildDelete(RawContacts.CONTENT_URI));
     }
 
     public void testTrimLeaveValid() {
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Test row that has type values with valid number
-        final EntityDelta state = EntityDeltaListTests.buildBeforeEntity(TEST_ID, VER_FIRST);
-        final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome);
+        final RawContactDelta state = RawContactDeltaListTests.buildBeforeEntity(mContext, TEST_ID,
+                VER_FIRST);
+        final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome);
         values.put(Phone.NUMBER, TEST_PHONE);
 
         // Build diff, expecting insert for data row and update enforcement
-        EntityDeltaListTests.assertDiffPattern(state,
-                EntityDeltaListTests.buildAssertVersion(VER_FIRST),
-                EntityDeltaListTests.buildUpdateAggregationSuspended(),
-                EntityDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
-                        EntityDeltaListTests.buildDataInsert(values, TEST_ID)),
-                EntityDeltaListTests.buildUpdateAggregationDefault());
+        RawContactDeltaListTests.assertDiffPattern(state,
+                RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
+                RawContactDeltaListTests.buildUpdateAggregationSuspended(),
+                RawContactDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
+                        RawContactDeltaListTests.buildDataInsert(values, TEST_ID)),
+                RawContactDeltaListTests.buildUpdateAggregationDefault());
 
         // Trim empty rows and try again, expecting no differences
-        EntityModifier.trimEmpty(state, source);
-        EntityDeltaListTests.assertDiffPattern(state,
-                EntityDeltaListTests.buildAssertVersion(VER_FIRST),
-                EntityDeltaListTests.buildUpdateAggregationSuspended(),
-                EntityDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
-                        EntityDeltaListTests.buildDataInsert(values, TEST_ID)),
-                EntityDeltaListTests.buildUpdateAggregationDefault());
+        RawContactModifier.trimEmpty(state, source);
+        RawContactDeltaListTests.assertDiffPattern(state,
+                RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
+                RawContactDeltaListTests.buildUpdateAggregationSuspended(),
+                RawContactDeltaListTests.buildOper(Data.CONTENT_URI, TYPE_INSERT,
+                        RawContactDeltaListTests.buildDataInsert(values, TEST_ID)),
+                RawContactDeltaListTests.buildUpdateAggregationDefault());
     }
 
     public void testTrimEmptyUntouched() {
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Build "before" that has empty row
-        final EntityDelta state = getEntity(TEST_ID);
+        final RawContactDelta state = getRawContact(TEST_ID);
         final ContentValues before = new ContentValues();
         before.put(Data._ID, TEST_ID);
         before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
@@ -454,7 +456,7 @@
         assertEquals("Unexpected operations", 0, diff.size());
 
         // Try trimming existing empty, which we shouldn't touch
-        EntityModifier.trimEmpty(state, source);
+        RawContactModifier.trimEmpty(state, source);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 0, diff.size());
@@ -463,7 +465,7 @@
     public void testTrimEmptyAfterUpdate() {
         final AccountType source = getAccountType();
         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Build "before" that has row with some phone number
         final ContentValues before = new ContentValues();
@@ -471,7 +473,7 @@
         before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
         before.put(kindPhone.typeColumn, typeHome.rawValue);
         before.put(Phone.NUMBER, TEST_PHONE);
-        final EntityDelta state = getEntity(TEST_ID, before);
+        final RawContactDelta state = getRawContact(TEST_ID, before);
 
         // Build diff, expecting no changes
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -501,7 +503,7 @@
         }
 
         // Now run trim, which should turn that update into delete
-        EntityModifier.trimEmpty(state, source);
+        RawContactModifier.trimEmpty(state, source);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 1, diff.size());
@@ -516,11 +518,11 @@
         final AccountType accountType = getAccountType();
         final AccountTypeManager accountTypes = getAccountTypes(accountType);
         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Try creating a contact without any child entries
-        final EntityDelta state = getEntity(null);
-        final EntityDeltaList set = EntityDeltaList.fromSingle(state);
+        final RawContactDelta state = getRawContact(null);
+        final RawContactDeltaList set = RawContactDeltaList.fromSingle(state);
 
         // Build diff, expecting single insert
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -533,7 +535,7 @@
         }
 
         // Trim empty rows and try again, expecting no insert
-        EntityModifier.trimEmpty(set, accountTypes);
+        RawContactModifier.trimEmpty(set, accountTypes);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 0, diff.size());
@@ -543,12 +545,12 @@
         final AccountType accountType = getAccountType();
         final AccountTypeManager accountTypes = getAccountTypes(accountType);
         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Try creating a contact with single empty entry
-        final EntityDelta state = getEntity(null);
-        EntityModifier.insertChild(state, kindPhone, typeHome);
-        final EntityDeltaList set = EntityDeltaList.fromSingle(state);
+        final RawContactDelta state = getRawContact(null);
+        RawContactModifier.insertChild(state, kindPhone, typeHome);
+        final RawContactDeltaList set = RawContactDeltaList.fromSingle(state);
 
         // Build diff, expecting two insert operations
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -566,7 +568,7 @@
         }
 
         // Trim empty rows and try again, expecting silence
-        EntityModifier.trimEmpty(set, accountTypes);
+        RawContactModifier.trimEmpty(set, accountTypes);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 0, diff.size());
@@ -576,7 +578,7 @@
         final AccountType accountType = getAccountType();
         final AccountTypeManager accountTypes = getAccountTypes(accountType);
         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Build "before" with two phone numbers
         final ContentValues first = new ContentValues();
@@ -591,8 +593,8 @@
         second.put(kindPhone.typeColumn, typeHome.rawValue);
         second.put(Phone.NUMBER, TEST_PHONE);
 
-        final EntityDelta state = getEntity(TEST_ID, first, second);
-        final EntityDeltaList set = EntityDeltaList.fromSingle(state);
+        final RawContactDelta state = getRawContact(TEST_ID, first, second);
+        final RawContactDeltaList set = RawContactDeltaList.fromSingle(state);
 
         // Build diff, expecting no changes
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -622,7 +624,7 @@
         }
 
         // Now run trim, which should turn that update into delete
-        EntityModifier.trimEmpty(set, accountTypes);
+        RawContactModifier.trimEmpty(set, accountTypes);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 3, diff.size());
@@ -647,7 +649,7 @@
         final AccountType accountType = getAccountType();
         final AccountTypeManager accountTypes = getAccountTypes(accountType);
         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
-        final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME);
+        final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
 
         // Build "before" with two phone numbers
         final ContentValues first = new ContentValues();
@@ -656,8 +658,8 @@
         first.put(kindPhone.typeColumn, typeHome.rawValue);
         first.put(Phone.NUMBER, TEST_PHONE);
 
-        final EntityDelta state = getEntity(TEST_ID, first);
-        final EntityDeltaList set = EntityDeltaList.fromSingle(state);
+        final RawContactDelta state = getRawContact(TEST_ID, first);
+        final RawContactDeltaList set = RawContactDeltaList.fromSingle(state);
 
         // Build diff, expecting no changes
         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
@@ -687,7 +689,7 @@
         }
 
         // Now run trim, which should turn into deleting the whole contact
-        EntityModifier.trimEmpty(set, accountTypes);
+        RawContactModifier.trimEmpty(set, accountTypes);
         diff.clear();
         state.buildDiff(diff);
         assertEquals("Unexpected operations", 1, diff.size());
@@ -708,10 +710,10 @@
         first.put(StructuredName.GIVEN_NAME, TEST_NAME);
 
         // Parse extras, making sure we keep single name
-        final EntityDelta state = getEntity(TEST_ID, first);
+        final RawContactDelta state = getRawContact(TEST_ID, first);
         final Bundle extras = new Bundle();
         extras.putString(Insert.NAME, TEST_NAME2);
-        EntityModifier.parseExtras(mContext, accountType, state, extras);
+        RawContactModifier.parseExtras(mContext, accountType, state, extras);
 
         final int nameCount = state.getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true);
         assertEquals("Unexpected names", 1, nameCount);
@@ -726,7 +728,7 @@
         first.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
         first.put(Im.DATA, TEST_IM);
 
-        final EntityDelta state = getEntity(TEST_ID, first);
+        final RawContactDelta state = getRawContact(TEST_ID, first);
         final int beforeCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
 
         // We should ignore data that doesn't fit account type rules, since account type
@@ -734,7 +736,7 @@
         final Bundle extras = new Bundle();
         extras.putInt(Insert.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
         extras.putString(Insert.IM_HANDLE, TEST_IM);
-        EntityModifier.parseExtras(mContext, accountType, state, extras);
+        RawContactModifier.parseExtras(mContext, accountType, state, extras);
 
         final int afterCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
         assertEquals("Broke account type rules", beforeCount, afterCount);
@@ -742,12 +744,12 @@
 
     public void testParseExtrasIgnoreUnhandled() {
         final AccountType accountType = getAccountType();
-        final EntityDelta state = getEntity(TEST_ID);
+        final RawContactDelta state = getRawContact(TEST_ID);
 
         // We should silently ignore types unsupported by account type
         final Bundle extras = new Bundle();
         extras.putString(Insert.POSTAL, TEST_POSTAL);
-        EntityModifier.parseExtras(mContext, accountType, state, extras);
+        RawContactModifier.parseExtras(mContext, accountType, state, extras);
 
         assertNull("Broke accoun type rules",
                 state.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE));
@@ -755,12 +757,12 @@
 
     public void testParseExtrasJobTitle() {
         final AccountType accountType = getAccountType();
-        final EntityDelta state = getEntity(TEST_ID);
+        final RawContactDelta state = getRawContact(TEST_ID);
 
         // Make sure that we create partial Organizations
         final Bundle extras = new Bundle();
         extras.putString(Insert.JOB_TITLE, TEST_NAME);
-        EntityModifier.parseExtras(mContext, accountType, state, extras);
+        RawContactModifier.parseExtras(mContext, accountType, state, extras);
 
         final int count = state.getMimeEntries(Organization.CONTENT_ITEM_TYPE).size();
         assertEquals("Expected to create organization", 1, count);
@@ -773,7 +775,7 @@
 
         ContactsMockContext context = new ContactsMockContext(getContext());
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
         mockNameValues.put(StructuredName.PREFIX, "prefix");
@@ -786,8 +788,8 @@
         mockNameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "PHONETIC_GIVEN");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateStructuredName(context, oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateStructuredName(context, oldState, newState, kind);
         List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
         assertEquals(1, list.size());
 
@@ -824,14 +826,14 @@
                         StructuredName.MIDDLE_NAME, StructuredName.FAMILY_NAME,
                         StructuredName.SUFFIX);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
         mockNameValues.put(StructuredName.DISPLAY_NAME, inputDisplayName);
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateStructuredName(context, oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateStructuredName(context, oldState, newState, kind);
         List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
         assertEquals(1, list.size());
 
@@ -866,7 +868,7 @@
                 .returnRow("prefix given middle family suffix")
                 .withProjection(StructuredName.DISPLAY_NAME);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
         mockNameValues.put(StructuredName.PREFIX, "prefix");
@@ -876,8 +878,8 @@
         mockNameValues.put(StructuredName.SUFFIX, "suffix");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateStructuredName(context, oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateStructuredName(context, oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -892,14 +894,14 @@
         AccountType newAccountType = new ExchangeAccountType(getContext(), "");
         DataKind kind = newAccountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
         mockNameValues.put(StructuredPostal.FORMATTED_ADDRESS, "formatted_address");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migratePostal(oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migratePostal(oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -915,7 +917,7 @@
         AccountType newAccountType = new GoogleAccountType(getContext(), "");
         DataKind kind = newAccountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
         mockNameValues.put(StructuredPostal.COUNTRY, "country");
@@ -925,8 +927,8 @@
         mockNameValues.put(StructuredPostal.STREET, "street");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migratePostal(oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migratePostal(oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -957,15 +959,15 @@
     private void testMigrateEventCommon(AccountType oldAccountType, AccountType newAccountType) {
         DataKind kind = newAccountType.getKindForMimetype(Event.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
         mockNameValues.put(Event.START_DATE, "1972-02-08");
         mockNameValues.put(Event.TYPE, Event.TYPE_BIRTHDAY);
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateEvent(oldState, newState, kind, 1990);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateEvent(oldState, newState, kind, 1990);
 
         List<ValuesDelta> list = newState.getMimeEntries(Event.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -981,7 +983,7 @@
         AccountType newAccountType = new ExchangeAccountType(getContext(), "");
         DataKind kind = newAccountType.getKindForMimetype(Event.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
         // No year format is not supported by Exchange.
@@ -995,8 +997,8 @@
         mockNameValues.put(Event.TYPE, Event.TYPE_ANNIVERSARY);
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateEvent(oldState, newState, kind, 1990);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateEvent(oldState, newState, kind, 1990);
 
         List<ValuesDelta> list = newState.getMimeEntries(Event.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -1013,7 +1015,7 @@
         AccountType newAccountType = new ExchangeAccountType(getContext(), "");
         DataKind kind = newAccountType.getKindForMimetype(Email.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
         mockNameValues.put(Email.TYPE, Email.TYPE_CUSTOM);
@@ -1037,8 +1039,8 @@
         mockNameValues.put(Email.ADDRESS, "address4");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(Email.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -1063,7 +1065,7 @@
         AccountType newAccountType = new ExchangeAccountType(getContext(), "");
         DataKind kind = newAccountType.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
         // Exchange doesn't support TYPE_HOME
@@ -1096,8 +1098,8 @@
         mockNameValues.put(Im.DATA, "im4");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(Im.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -1149,7 +1151,7 @@
         // - "3" -- MOBILE
         // - "4" -- WORK
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
         mockNameValues.put(Phone.TYPE, Phone.TYPE_HOME);
@@ -1179,8 +1181,8 @@
         mockNameValues.put(Phone.NUMBER, "5");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(Phone.CONTENT_ITEM_TYPE);
         assertNotNull(list);
@@ -1208,15 +1210,15 @@
         AccountType newAccountType = new ExchangeAccountType(getContext(), "");
         DataKind kind = newAccountType.getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
 
-        EntityDelta oldState = new EntityDelta();
+        RawContactDelta oldState = new RawContactDelta();
         ContentValues mockNameValues = new ContentValues();
         mockNameValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
         mockNameValues.put(Organization.COMPANY, "company1");
         mockNameValues.put(Organization.DEPARTMENT, "department1");
         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
 
-        EntityDelta newState = new EntityDelta();
-        EntityModifier.migrateGenericWithoutTypeColumn(oldState, newState, kind);
+        RawContactDelta newState = new RawContactDelta();
+        RawContactModifier.migrateGenericWithoutTypeColumn(oldState, newState, kind);
 
         List<ValuesDelta> list = newState.getMimeEntries(Organization.CONTENT_ITEM_TYPE);
         assertNotNull(list);
diff --git a/tests/src/com/android/contacts/activities/PeopleActivityTest.java b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
index c0648a7..11fccd1 100644
--- a/tests/src/com/android/contacts/activities/PeopleActivityTest.java
+++ b/tests/src/com/android/contacts/activities/PeopleActivityTest.java
@@ -38,10 +38,10 @@
 import com.android.contacts.detail.ContactDetailFragment;
 import com.android.contacts.interactions.TestLoaderManager;
 import com.android.contacts.list.ContactBrowseListFragment;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.BaseAccountType;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.BaseAccountType;
 import com.android.contacts.test.InjectedServices;
 import com.android.contacts.tests.mocks.ContactsMockContext;
 import com.android.contacts.tests.mocks.MockAccountTypeManager;
diff --git a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
index b252c9c..b1d1daa 100644
--- a/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
+++ b/tests/src/com/android/contacts/detail/ContactDetailFragmentTests.java
@@ -25,6 +25,9 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.contacts.detail.ContactDetailFragment.DetailViewEntry;
+import com.android.contacts.model.dataitem.DataItem;
+import com.android.contacts.model.dataitem.EmailDataItem;
+import com.android.contacts.model.dataitem.ImDataItem;
 
 /**
  * Tests for {@link ContactDetailFragment}.
@@ -41,9 +44,10 @@
         values.put(Im.TYPE, Im.TYPE_HOME);
         values.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
         values.put(Im.DATA, TEST_ADDRESS);
+        ImDataItem im = (ImDataItem) DataItem.createFrom(null, values);
 
         DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
-        ContactDetailFragment.buildImActions(mContext, entry, values);
+        ContactDetailFragment.buildImActions(mContext, entry, im);
         assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
         assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
 
@@ -58,9 +62,10 @@
         values.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
         values.put(Im.DATA, TEST_ADDRESS);
         values.put(Im.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO);
+        ImDataItem im = (ImDataItem) DataItem.createFrom(null, values);
 
         DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
-        ContactDetailFragment.buildImActions(mContext, entry, values);
+        ContactDetailFragment.buildImActions(mContext, entry, im);
         assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
         assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
 
@@ -77,9 +82,10 @@
         values.put(Im.DATA, TEST_ADDRESS);
         values.put(Im.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO |
                 Im.CAPABILITY_HAS_VOICE);
+        ImDataItem im = (ImDataItem) DataItem.createFrom(null, values);
 
         DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
-        ContactDetailFragment.buildImActions(mContext, entry, values);
+        ContactDetailFragment.buildImActions(mContext, entry, im);
         assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
         assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
 
@@ -96,9 +102,10 @@
         values.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
         values.put(Im.CUSTOM_PROTOCOL, TEST_PROTOCOL);
         values.put(Im.DATA, TEST_ADDRESS);
+        ImDataItem im = (ImDataItem) DataItem.createFrom(null, values);
 
         DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
-        ContactDetailFragment.buildImActions(mContext, entry, values);
+        ContactDetailFragment.buildImActions(mContext, entry, im);
         assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
 
         final Uri data = entry.intent.getData();
@@ -119,9 +126,11 @@
         values.put(Email.DATA, TEST_ADDRESS);
         values.put(Email.CHAT_CAPABILITY, Im.CAPABILITY_HAS_VOICE | Im.CAPABILITY_HAS_VIDEO |
                 Im.CAPABILITY_HAS_VOICE);
+        ImDataItem im = ImDataItem.createFromEmail(
+                (EmailDataItem) DataItem.createFrom(null, values));
 
         DetailViewEntry entry = new ContactDetailFragment.DetailViewEntry();
-        ContactDetailFragment.buildImActions(mContext, entry, values);
+        ContactDetailFragment.buildImActions(mContext, entry, im);
         assertEquals(Intent.ACTION_SENDTO, entry.intent.getAction());
         assertEquals("xmpp:" + TEST_ADDRESS + "?message", entry.intent.getData().toString());
 
diff --git a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
index a60f2c8..9f2d49b 100644
--- a/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
+++ b/tests/src/com/android/contacts/editor/ContactEditorUtilsTest.java
@@ -20,8 +20,8 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.android.contacts.tests.mocks.MockAccountTypeManager;
 import com.google.common.collect.Sets;
 
diff --git a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
index 8bcebcd..fcbd83d 100644
--- a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
@@ -26,9 +26,9 @@
 
 import com.android.contacts.ContactsApplication;
 import com.android.contacts.R;
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.BaseAccountType;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.BaseAccountType;
 import com.android.contacts.test.FragmentTestActivity;
 import com.android.contacts.test.InjectedServices;
 import com.android.contacts.tests.mocks.ContactsMockContext;
diff --git a/tests/src/com/android/contacts/model/AccountTypeManagerTest.java b/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
index c3b9e22..c8db85e 100644
--- a/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
+++ b/tests/src/com/android/contacts/model/AccountTypeManagerTest.java
@@ -20,6 +20,9 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountTypeWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
diff --git a/tests/src/com/android/contacts/model/AccountWithDataSetTest.java b/tests/src/com/android/contacts/model/AccountWithDataSetTest.java
index 30ada1b..1818c38 100644
--- a/tests/src/com/android/contacts/model/AccountWithDataSetTest.java
+++ b/tests/src/com/android/contacts/model/AccountWithDataSetTest.java
@@ -21,6 +21,7 @@
 import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.google.common.collect.Lists;
 
 import java.util.List;
diff --git a/tests/src/com/android/contacts/ContactLoaderTest.java b/tests/src/com/android/contacts/model/ContactLoaderTest.java
similarity index 92%
rename from tests/src/com/android/contacts/ContactLoaderTest.java
rename to tests/src/com/android/contacts/model/ContactLoaderTest.java
index b1d9d86..54d220f 100644
--- a/tests/src/com/android/contacts/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/model/ContactLoaderTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts;
+package com.android.contacts.model;
 
 import android.content.ContentUris;
 import android.net.Uri;
@@ -28,9 +28,10 @@
 import android.test.LoaderTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import com.android.contacts.model.AccountType;
-import com.android.contacts.model.AccountWithDataSet;
-import com.android.contacts.model.BaseAccountType;
+import com.android.contacts.model.Contact;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.model.account.BaseAccountType;
 import com.android.contacts.test.InjectedServices;
 import com.android.contacts.tests.mocks.ContactsMockContext;
 import com.android.contacts.tests.mocks.MockAccountTypeManager;
@@ -74,23 +75,23 @@
         super.tearDown();
     }
 
-    private ContactLoader.Result assertLoadContact(Uri uri) {
+    private Contact assertLoadContact(Uri uri) {
         final ContactLoader loader = new ContactLoader(mMockContext, uri, true);
         return getLoaderResultSynchronously(loader);
     }
 
     public void testNullUri() {
-        ContactLoader.Result result = assertLoadContact(null);
+        Contact result = assertLoadContact(null);
         assertTrue(result.isError());
     }
 
     public void testEmptyUri() {
-        ContactLoader.Result result = assertLoadContact(Uri.EMPTY);
+        Contact result = assertLoadContact(Uri.EMPTY);
         assertTrue(result.isError());
     }
 
     public void testInvalidUri() {
-        ContactLoader.Result result = assertLoadContact(Uri.parse("content://wtf"));
+        Contact result = assertLoadContact(Uri.parse("content://wtf"));
         assertTrue(result.isError());
     }
 
@@ -111,14 +112,14 @@
         mContactsProvider.expectTypeQuery(baseUri, Contacts.CONTENT_ITEM_TYPE);
         queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
 
-        ContactLoader.Result contact = assertLoadContact(baseUri);
+        Contact contact = assertLoadContact(baseUri);
 
         assertEquals(contactId, contact.getId());
         assertEquals(rawContactId, contact.getNameRawContactId());
         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
         assertEquals(lookupKey, contact.getLookupKey());
         assertEquals(lookupUri, contact.getLookupUri());
-        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
         mContactsProvider.verify();
     }
@@ -143,14 +144,14 @@
         queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, lookupKey);
         queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
 
-        ContactLoader.Result contact = assertLoadContact(legacyUri);
+        Contact contact = assertLoadContact(legacyUri);
 
         assertEquals(contactId, contact.getId());
         assertEquals(rawContactId, contact.getNameRawContactId());
         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
         assertEquals(lookupKey, contact.getLookupKey());
         assertEquals(lookupUri, contact.getLookupUri());
-        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
         mContactsProvider.verify();
     }
@@ -174,14 +175,14 @@
         queries.fetchContactIdAndLookupFromRawContactUri(rawContactUri, contactId, lookupKey);
         queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
 
-        ContactLoader.Result contact = assertLoadContact(rawContactUri);
+        Contact contact = assertLoadContact(rawContactUri);
 
         assertEquals(contactId, contact.getId());
         assertEquals(rawContactId, contact.getNameRawContactId());
         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
         assertEquals(lookupKey, contact.getLookupKey());
         assertEquals(lookupUri, contact.getLookupUri());
-        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
         mContactsProvider.verify();
     }
@@ -203,14 +204,14 @@
         mContactsProvider.expectTypeQuery(lookupNoIdUri, Contacts.CONTENT_ITEM_TYPE);
         queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
 
-        ContactLoader.Result contact = assertLoadContact(lookupNoIdUri);
+        Contact contact = assertLoadContact(lookupNoIdUri);
 
         assertEquals(contactId, contact.getId());
         assertEquals(rawContactId, contact.getNameRawContactId());
         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
         assertEquals(lookupKey, contact.getLookupKey());
         assertEquals(lookupUri, contact.getLookupUri());
-        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
         mContactsProvider.verify();
     }
@@ -232,14 +233,14 @@
         mContactsProvider.expectTypeQuery(lookupUri, Contacts.CONTENT_ITEM_TYPE);
         queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
 
-        ContactLoader.Result contact = assertLoadContact(lookupUri);
+        Contact contact = assertLoadContact(lookupUri);
 
         assertEquals(contactId, contact.getId());
         assertEquals(rawContactId, contact.getNameRawContactId());
         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
         assertEquals(lookupKey, contact.getLookupKey());
         assertEquals(lookupUri, contact.getLookupUri());
-        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
         mContactsProvider.verify();
     }
@@ -271,14 +272,14 @@
         mContactsProvider.expectTypeQuery(lookupWithWrongIdUri, Contacts.CONTENT_ITEM_TYPE);
         queries.fetchAllData(entityUri, contactId, rawContactId, dataId, lookupKey);
 
-        ContactLoader.Result contact = assertLoadContact(lookupWithWrongIdUri);
+        Contact contact = assertLoadContact(lookupWithWrongIdUri);
 
         assertEquals(contactId, contact.getId());
         assertEquals(rawContactId, contact.getNameRawContactId());
         assertEquals(DisplayNameSources.STRUCTURED_NAME, contact.getDisplayNameSource());
         assertEquals(lookupKey, contact.getLookupKey());
         assertEquals(lookupUri, contact.getLookupUri());
-        assertEquals(1, contact.getEntities().size());
+        assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
 
         mContactsProvider.verify();
diff --git a/tests/src/com/android/contacts/model/AccountTypeTest.java b/tests/src/com/android/contacts/model/account/AccountTypeTest.java
similarity index 97%
rename from tests/src/com/android/contacts/model/AccountTypeTest.java
rename to tests/src/com/android/contacts/model/account/AccountTypeTest.java
index 5ed46f5..ad111d9 100644
--- a/tests/src/com/android/contacts/model/AccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/account/AccountTypeTest.java
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.Context;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.contacts.model.account.AccountType;
 import com.android.contacts.tests.R;
 
 /**
diff --git a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java b/tests/src/com/android/contacts/model/account/ExternalAccountTypeTest.java
similarity index 96%
rename from tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
rename to tests/src/com/android/contacts/model/account/ExternalAccountTypeTest.java
index e4a94e0..9545bc1 100644
--- a/tests/src/com/android/contacts/model/ExternalAccountTypeTest.java
+++ b/tests/src/com/android/contacts/model/account/ExternalAccountTypeTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.contacts.model;
+package com.android.contacts.model.account;
 
 import android.content.Context;
 import android.provider.ContactsContract.CommonDataKinds.Email;
@@ -31,6 +31,11 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.BaseAccountType;
+import com.android.contacts.model.account.ExternalAccountType;
+import com.android.contacts.model.account.FallbackAccountType;
+import com.android.contacts.model.dataitem.DataKind;
 import com.android.contacts.tests.R;
 
 import java.util.List;
diff --git a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
index 635607a..d774252 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockAccountTypeManager.java
@@ -15,10 +15,10 @@
  */
 package com.android.contacts.tests.mocks;
 
-import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountTypeManager;
-import com.android.contacts.model.AccountTypeWithDataSet;
-import com.android.contacts.model.AccountWithDataSet;
+import com.android.contacts.model.account.AccountType;
+import com.android.contacts.model.account.AccountTypeWithDataSet;
+import com.android.contacts.model.account.AccountWithDataSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
diff --git a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
index bc54e5b..20229d2 100644
--- a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
+++ b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
@@ -32,7 +32,7 @@
 import android.widget.Button;
 import android.widget.Toast;
 
-import com.android.contacts.model.GoogleAccountType;
+import com.android.contacts.model.account.GoogleAccountType;
 import com.android.contacts.tests.R;
 import com.google.common.collect.Lists;