Implement insert and update method for ContactMetadataProvider.

Insert and update values to MetadataSync table, and also parse the data
column to MetadataEntry object, and then update other related tables
for the raw contact.

BUG 20055193

Change-Id: Ice7a6db308995704e7a5fb12b9594be03fa39cd1
diff --git a/src/com/android/providers/contacts/ContactMetadataProvider.java b/src/com/android/providers/contacts/ContactMetadataProvider.java
index 31dca7d..cd545ad 100644
--- a/src/com/android/providers/contacts/ContactMetadataProvider.java
+++ b/src/com/android/providers/contacts/ContactMetadataProvider.java
@@ -20,6 +20,7 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.IContentProvider;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
@@ -27,12 +28,15 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.MetadataSync;
 import android.text.TextUtils;
 import android.util.Log;
 import com.android.common.content.ProjectionMap;
 import com.android.providers.contacts.ContactsDatabaseHelper.MetadataSyncColumns;
+import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
 import com.android.providers.contacts.ContactsDatabaseHelper.Views;
+import com.android.providers.contacts.MetadataEntryParser.MetadataEntry;
 import com.android.providers.contacts.util.SelectionBuilder;
 import com.android.providers.contacts.util.UserUtils;
 import com.google.common.annotations.VisibleForTesting;
@@ -71,11 +75,17 @@
             .build();
 
     private ContactsDatabaseHelper mDbHelper;
+    private ContactsProvider2 mContactsProvider;
 
     @Override
     public boolean onCreate() {
         final Context context = getContext();
         mDbHelper = getDatabaseHelper(context);
+        final IContentProvider iContentProvider = context.getContentResolver().acquireProvider(
+                ContactsContract.AUTHORITY);
+        final ContentProvider provider = ContentProvider.coerceToLocalContentProvider(
+                iContentProvider);
+        mContactsProvider = (ContactsProvider2) provider;
         return true;
     }
 
@@ -141,40 +151,9 @@
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
-
-        if (sURIMatcher.match(uri) != METADATA_SYNC) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Calling contact metadata insert on an unknown/invalid URI" , uri));
-        }
-
-        // Don't insert deleted metadata.
-        Integer deleted = values.getAsInteger(MetadataSync.DELETED);
-        if (deleted != null && deleted != 0) {
-            // Cannot insert deleted metadata
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Cannot insert deleted metadata:" + values.toString(), uri));
-        }
-
-        // Insert the new entry.
-        // Populate the relevant values before inserting the new entry into the database.
-        final Long accountId = replaceAccountInfoByAccountId(uri, values);
-        final String rawContactBackupId = values.getAsString(MetadataSync.RAW_CONTACT_BACKUP_ID);
-        final String data = values.getAsString(MetadataSync.DATA);
-        deleted = 0; //Only insert non-deleted metadata
-
-        if (accountId == null || rawContactBackupId == null) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Invalid identifier is found: accountId=" + accountId + "; " +
-                            "rawContactBackupId=" + rawContactBackupId, uri));
-        }
-
-        Long metadataSyncId = mDbHelper.replaceMetadataSync(rawContactBackupId, accountId, data,
-                deleted);
-        if (metadataSyncId < 0) {
-            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
-                    "Metadata insertion failed. Values= " + values.toString(), uri));
-        }
-
+        // Insert the new entry, and also parse the data column to update related tables.
+        final long metadataSyncId = updateOrInsertDataToMetadataSync(
+                uri, values, /* isInsert = */ true);
         return ContentUris.withAppendedId(uri, metadataSyncId);
     }
 
@@ -185,7 +164,79 @@
 
     @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        return 0;
+        // Update the metadata entry and parse the data column to update related tables.
+        updateOrInsertDataToMetadataSync(uri, values, /* isInsert = */ false);
+        return 1;
+    }
+
+    /**
+     * Insert or update a non-deleted entry to MetadataSync table, and also parse the data column
+     * to update related tables for the raw contact.
+     * Set 'isInsert' as true if it's insert and false if update.
+     * Returns new inserted metadataSyncId if it's insert, and returns 1 if it's update.
+     */
+    private long updateOrInsertDataToMetadataSync(Uri uri, ContentValues values, boolean isInsert) {
+        final int matchUri = sURIMatcher.match(uri);
+        if ((isInsert && matchUri != METADATA_SYNC) ||
+                (!isInsert && matchUri != METADATA_SYNC && matchUri != METADATA_SYNC_ID)) {
+            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+                    "Calling contact metadata insert or update on an unknown/invalid URI", uri));
+        }
+
+        // Don't insert or update a deleted metadata.
+        Integer deleted = values.getAsInteger(MetadataSync.DELETED);
+        if (deleted != null && deleted != 0) {
+            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+                    "Cannot insert or update deleted metadata:" + values.toString(), uri));
+        }
+
+        // Check if data column is empty or null.
+        final String data = values.getAsString(MetadataSync.DATA);
+        if (TextUtils.isEmpty(data)) {
+            throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+                    "Data column cannot be empty.", uri));
+        }
+
+        long result = 0;
+        final SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        if (matchUri == METADATA_SYNC_ID) {
+            // Update for the metadataSyncId.
+            final long metadataSyncId = ContentUris.parseId(uri);
+            final String selection = MetadataSync._ID + "=?";
+            final String[] selectionArgs = new String[1];
+            selectionArgs[0] = String.valueOf(metadataSyncId);
+            db.update(Tables.METADATA_SYNC, values, selection, selectionArgs);
+            result = 1;
+        } else {
+            // Update or insert for backupId and account info.
+            final Long accountId = replaceAccountInfoByAccountId(uri, values);
+            final String rawContactBackupId = values.getAsString(MetadataSync.RAW_CONTACT_BACKUP_ID);
+            deleted = 0; //Only insert or update non-deleted metadata
+            if (accountId == null || rawContactBackupId == null) {
+                throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+                        "Invalid identifier is found: accountId=" + accountId + "; " +
+                                "rawContactBackupId=" + rawContactBackupId, uri));
+            }
+
+            if (isInsert) {
+                result = mDbHelper.insertMetadataSync(rawContactBackupId, accountId, data, deleted);
+                if (result <= 0) {
+                    throw new IllegalArgumentException(mDbHelper.exceptionMessage(
+                            "Metadata insertion failed. Values= " + values.toString(), uri));
+                }
+            } else {
+                mDbHelper.updateMetadataSync(rawContactBackupId, accountId, data, deleted);
+                result = 1;
+            }
+        }
+
+        // Parse the data column and update other tables.
+        // Data field will never be empty or null, since contacts prefs and usage stats
+        // have default values.
+        final MetadataEntry metadataEntry = MetadataEntryParser.parseDataToMetaDataEntry(data);
+        mContactsProvider.updateFromMetaDataEntry(db, metadataEntry);
+
+        return result;
     }
 
     /**
@@ -220,4 +271,4 @@
 
         return id;
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
index 59b899e..6df4e1d 100644
--- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java
+++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@@ -993,7 +993,8 @@
     private SQLiteStatement mStatusUpdateDelete;
     private SQLiteStatement mResetNameVerifiedForOtherRawContacts;
     private SQLiteStatement mContactInDefaultDirectoryQuery;
-    private SQLiteStatement mMetadataSyncReplace;
+    private SQLiteStatement mMetadataSyncInsert;
+    private SQLiteStatement mMetadataSyncUpdate;
 
     private StringBuilder mSb = new StringBuilder();
 
@@ -3668,7 +3669,7 @@
     private void upgradeToVersion401(SQLiteDatabase db) {
         db.execSQL("CREATE TABLE " + Tables.VISIBLE_CONTACTS + " (" +
                 Contacts._ID + " INTEGER PRIMARY KEY" +
-        ");");
+                ");");
         db.execSQL("INSERT INTO " + Tables.VISIBLE_CONTACTS +
                 " SELECT " + Contacts._ID +
                 " FROM " + Tables.CONTACTS +
@@ -3823,8 +3824,8 @@
                 " ADD " + Groups.GROUP_IS_READ_ONLY + " INTEGER NOT NULL DEFAULT 0");
         db.execSQL(
                 "UPDATE " + Tables.GROUPS +
-                "   SET " + Groups.GROUP_IS_READ_ONLY + "=1" +
-                " WHERE " + Groups.SYSTEM_ID + " NOT NULL");
+                        "   SET " + Groups.GROUP_IS_READ_ONLY + "=1" +
+                        " WHERE " + Groups.SYSTEM_ID + " NOT NULL");
     }
 
     private void upgradeToVersion416(SQLiteDatabase db) {
@@ -5000,14 +5001,14 @@
         }
         final SQLiteStatement select = getWritableDatabase().compileStatement(
                 "SELECT " + AccountsColumns._ID +
-                " FROM " + Tables.ACCOUNTS +
-                " WHERE " +
-                "((?1 IS NULL AND " + AccountsColumns.ACCOUNT_NAME + " IS NULL) OR " +
-                "(" + AccountsColumns.ACCOUNT_NAME + "=?1)) AND " +
-                "((?2 IS NULL AND " + AccountsColumns.ACCOUNT_TYPE + " IS NULL) OR " +
-                "(" + AccountsColumns.ACCOUNT_TYPE + "=?2)) AND " +
-                "((?3 IS NULL AND " + AccountsColumns.DATA_SET + " IS NULL) OR " +
-                "(" + AccountsColumns.DATA_SET + "=?3))");
+                        " FROM " + Tables.ACCOUNTS +
+                        " WHERE " +
+                        "((?1 IS NULL AND " + AccountsColumns.ACCOUNT_NAME + " IS NULL) OR " +
+                        "(" + AccountsColumns.ACCOUNT_NAME + "=?1)) AND " +
+                        "((?2 IS NULL AND " + AccountsColumns.ACCOUNT_TYPE + " IS NULL) OR " +
+                        "(" + AccountsColumns.ACCOUNT_TYPE + "=?2)) AND " +
+                        "((?3 IS NULL AND " + AccountsColumns.DATA_SET + " IS NULL) OR " +
+                        "(" + AccountsColumns.DATA_SET + "=?3))");
         try {
             DatabaseUtils.bindObjectToProgram(select, 1, accountWithDataSet.getAccountName());
             DatabaseUtils.bindObjectToProgram(select, 2, accountWithDataSet.getAccountType());
@@ -6036,21 +6037,39 @@
                 new String[] {String.valueOf(contactId)});
     }
 
-    public long replaceMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
-        if (mMetadataSyncReplace == null) {
-            mMetadataSyncReplace = getWritableDatabase().compileStatement(
-                    "INSERT OR REPLACE INTO " + Tables.METADATA_SYNC + "("
+    public long insertMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
+        if (mMetadataSyncInsert == null) {
+            mMetadataSyncInsert = getWritableDatabase().compileStatement(
+                    "INSERT INTO " + Tables.METADATA_SYNC + "("
                             + MetadataSync.RAW_CONTACT_BACKUP_ID + ", "
                             + MetadataSyncColumns.ACCOUNT_ID + ", "
                             + MetadataSync.DATA + ","
                             + MetadataSync.DELETED + ")" +
                             " VALUES (?,?,?,?)");
         }
-        mMetadataSyncReplace.bindString(1, backupId);
-        mMetadataSyncReplace.bindLong(2, accountId);
+        mMetadataSyncInsert.bindString(1, backupId);
+        mMetadataSyncInsert.bindLong(2, accountId);
         data = (data == null) ? "" : data;
-        mMetadataSyncReplace.bindString(3, data);
-        mMetadataSyncReplace.bindLong(4, deleted);
-        return mMetadataSyncReplace.executeInsert();
+        mMetadataSyncInsert.bindString(3, data);
+        mMetadataSyncInsert.bindLong(4, deleted);
+        return mMetadataSyncInsert.executeInsert();
+    }
+
+    public void updateMetadataSync(String backupId, Long accountId, String data, Integer deleted) {
+        if (mMetadataSyncUpdate == null) {
+            mMetadataSyncUpdate = getWritableDatabase().compileStatement(
+                    "UPDATE " + Tables.METADATA_SYNC
+                            + " SET " + MetadataSync.DATA + "=?,"
+                            + MetadataSync.DELETED + "=?"
+                            + " WHERE " + MetadataSync.RAW_CONTACT_BACKUP_ID + "=? AND "
+                            + MetadataSyncColumns.ACCOUNT_ID + "=?");
+        }
+
+        data = (data == null) ? "" : data;
+        mMetadataSyncUpdate.bindString(1, data);
+        mMetadataSyncUpdate.bindLong(2, deleted);
+        mMetadataSyncUpdate.bindString(3, backupId);
+        mMetadataSyncUpdate.bindLong(4, accountId);
+        mMetadataSyncUpdate.execute();
     }
 }
diff --git a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
index 7f45ac8..be1b909 100644
--- a/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
+++ b/tests/src/com/android/providers/contacts/ContactMetadataProviderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.providers.contacts;
 
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
@@ -41,9 +42,23 @@
 public class ContactMetadataProviderTest extends BaseContactsProvider2Test {
     private static String TEST_ACCOUNT_TYPE = "test_account_type";
     private static String TEST_ACCOUNT_NAME = "test_account_name";
-    private static String TEST_DATA_SET = "test_data_set";
+    private static String TEST_DATA_SET = "plus";
     private static String TEST_BACKUP_ID = "1001";
-    private static String TEST_DATA = "test_data";
+    private static String TEST_DATA = "{\n" +
+            "  \"unique_contact_id\": {\n" +
+            "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+            "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+            "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+            "    \"contact_id\": " + TEST_BACKUP_ID + ",\n" +
+            "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+            "  },\n" +
+            "  \"contact_prefs\": {\n" +
+            "    \"send_to_voicemail\": true,\n" +
+            "    \"starred\": true,\n" +
+            "    \"pinned\": 2\n" +
+            "  }\n" +
+            "  }";
+
     private static String SELECTION_BY_TEST_ACCOUNT = MetadataSync.ACCOUNT_NAME + "='" +
             TEST_ACCOUNT_NAME + "' AND " + MetadataSync.ACCOUNT_TYPE + "='" + TEST_ACCOUNT_TYPE +
             "' AND " + MetadataSync.DATA_SET + "='" + TEST_DATA_SET + "'";
@@ -56,7 +71,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         mContactMetadataProvider = (ContactMetadataProvider) addProvider(
-               ContactMetadataProvider.class, MetadataSync.METADATA_AUTHORITY);
+                ContactMetadataProvider.class, MetadataSync.METADATA_AUTHORITY);
         // Reset the dbHelper to be the one ContactsProvider2 is using. Before this, two providers
         // are using different dbHelpers.
         mContactMetadataProvider.setDatabaseHelper(((SynchronousContactsProvider2)
@@ -74,6 +89,16 @@
         }
     }
 
+    public void testUpdateWithInvalidUri() {
+        try {
+            mResolver.update(Uri.withAppendedPath(MetadataSync.METADATA_AUTHORITY_URI,
+                    "metadata"), getDefaultValues(), null, null);
+            fail("the update was expected to fail, but it succeeded");
+        } catch (IllegalArgumentException e) {
+            // this was expected
+        }
+    }
+
     public void testGetMetadataByAccount() {
         Cursor c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT,
                 null, null);
@@ -86,30 +111,253 @@
         c.close();
     }
 
-    public void testReplaceMetadataForSameAccountIdAndBackupId() {
-        //Insert a new metadata with same account and backupId as defaultValues, but different data
-        //field.
+    public void testFailOnInsertMetadataForSameAccountIdAndBackupId() {
+        // Insert a new metadata with same account and backupId as defaultValues should fail.
+        String newData = "{\n" +
+                "  \"unique_contact_id\": {\n" +
+                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+                "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+                "    \"contact_id\": " + TEST_BACKUP_ID + ",\n" +
+                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+                "  },\n" +
+                "  \"contact_prefs\": {\n" +
+                "    \"send_to_voicemail\": false,\n" +
+                "    \"starred\": false,\n" +
+                "    \"pinned\": 1\n" +
+                "  }\n" +
+                "  }";
+
         ContentValues  newValues =  new ContentValues();
         newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
         newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
         newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
         newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID);
-        newValues.put(MetadataSync.DATA, "new data");
+        newValues.put(MetadataSync.DATA, newData);
         newValues.put(MetadataSync.DELETED, 0);
-        mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+        try {
+            mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+        } catch (Exception e) {
+            // Expected.
+        }
+    }
 
-        // Total two metadata entries
-        Cursor c = mResolver.query(MetadataSync.CONTENT_URI, null, null,
-                null, null);
-        assertEquals(2, c.getCount());
+    public void testInsertAndUpdateMetadataSync() {
+        // Create a raw contact with backupId.
+        String backupId = "backupId10001";
+        long rawContactId = RawContactUtil.createRawContactWithAccountDataSet(
+                mResolver, mTestAccount);
+        Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+        ContentValues values = new ContentValues();
+        values.put(RawContacts.BACKUP_ID, backupId);
+        assertEquals(1, mResolver.update(rawContactUri, values, null, null));
 
-        // Only one metadata entry for TEST_ACCOUNT
-        c = mResolver.query(MetadataSync.CONTENT_URI, null, SELECTION_BY_TEST_ACCOUNT, null, null);
+        assertStoredValue(rawContactUri, RawContacts._ID, rawContactId);
+        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
+        assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET);
+
+        String deleted = "0";
+        String insertJson = "{\n" +
+                "  \"unique_contact_id\": {\n" +
+                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+                "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+                "    \"contact_id\": " + backupId + ",\n" +
+                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+                "  },\n" +
+                "  \"contact_prefs\": {\n" +
+                "    \"send_to_voicemail\": true,\n" +
+                "    \"starred\": true,\n" +
+                "    \"pinned\": 2\n" +
+                "  }\n" +
+                "  }";
+
+        // Insert to MetadataSync table.
+        ContentValues insertedValues = new ContentValues();
+        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+        insertedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        insertedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        insertedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
+        insertedValues.put(MetadataSync.DATA, insertJson);
+        insertedValues.put(MetadataSync.DELETED, deleted);
+        Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+        long metadataId = ContentUris.parseId(metadataUri);
+        assertEquals(true, metadataId > 0);
+
+        // Check if RawContact table is updated  after inserting metadata.
+        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        assertStoredValue(rawContactUri, RawContacts.BACKUP_ID, backupId);
+        assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET);
+        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "1");
+        assertStoredValue(rawContactUri, RawContacts.STARRED, "1");
+        assertStoredValue(rawContactUri, RawContacts.PINNED, "2");
+
+        // Update the MetadataSync table.
+        String updatedJson = "{\n" +
+                "  \"unique_contact_id\": {\n" +
+                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+                "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+                "    \"contact_id\": " + backupId + ",\n" +
+                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+                "  },\n" +
+                "  \"contact_prefs\": {\n" +
+                "    \"send_to_voicemail\": false,\n" +
+                "    \"starred\": false,\n" +
+                "    \"pinned\": 1\n" +
+                "  }\n" +
+                "  }";
+        ContentValues updatedValues = new ContentValues();
+        updatedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+        updatedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        updatedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        updatedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
+        updatedValues.put(MetadataSync.DATA, updatedJson);
+        updatedValues.put(MetadataSync.DELETED, deleted);
+        assertEquals(1, mResolver.update(MetadataSync.CONTENT_URI, updatedValues, null, null));
+
+        // Check if the update is correct.
+        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        assertStoredValue(rawContactUri, RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        assertStoredValue(rawContactUri, RawContacts.DATA_SET, TEST_DATA_SET);
+        assertStoredValue(rawContactUri, RawContacts.SEND_TO_VOICEMAIL, "0");
+        assertStoredValue(rawContactUri, RawContacts.STARRED, "0");
+        assertStoredValue(rawContactUri, RawContacts.PINNED, "1");
+    }
+
+    public void testInsertMetadataWithoutUpdateTables() {
+        // If raw contact doesn't exist, don't update raw contact tables.
+        String backupId = "newBackupId";
+        String deleted = "0";
+        String insertJson = "{\n" +
+                "  \"unique_contact_id\": {\n" +
+                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+                "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+                "    \"contact_id\": " + backupId + ",\n" +
+                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+                "  },\n" +
+                "  \"contact_prefs\": {\n" +
+                "    \"send_to_voicemail\": true,\n" +
+                "    \"starred\": true,\n" +
+                "    \"pinned\": 2\n" +
+                "  }\n" +
+                "  }";
+
+        // Insert to MetadataSync table.
+        ContentValues insertedValues = new ContentValues();
+        insertedValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+        insertedValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        insertedValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        insertedValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
+        insertedValues.put(MetadataSync.DATA, insertJson);
+        insertedValues.put(MetadataSync.DELETED, deleted);
+        Uri metadataUri = mResolver.insert(MetadataSync.CONTENT_URI, insertedValues);
+
+        long metadataId = ContentUris.parseId(metadataUri);
+        assertEquals(true, metadataId > 0);
+    }
+
+    public void testFailUpdateDeletedMetadata() {
+        String backupId = "backupId001";
+        String newData = "{\n" +
+                "  \"unique_contact_id\": {\n" +
+                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+                "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+                "    \"contact_id\": " + backupId + ",\n" +
+                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+                "  },\n" +
+                "  \"contact_prefs\": {\n" +
+                "    \"send_to_voicemail\": false,\n" +
+                "    \"starred\": false,\n" +
+                "    \"pinned\": 1\n" +
+                "  }\n" +
+                "  }";
+
+        ContentValues  newValues =  new ContentValues();
+        newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
+        newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+        newValues.put(MetadataSync.DATA, newData);
+        newValues.put(MetadataSync.DELETED, 1);
+
+        try {
+            mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+            fail("the update was expected to fail, but it succeeded");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testInsertWithNullData() {
+        ContentValues  newValues =  new ContentValues();
+        String data = null;
+        String backupId = "backupId002";
+        newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
+        newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, backupId);
+        newValues.put(MetadataSync.DATA, data);
+        newValues.put(MetadataSync.DELETED, 0);
+
+        try {
+            mResolver.insert(MetadataSync.CONTENT_URI, newValues);
+        } catch (IllegalArgumentException e) {
+            // Expected.
+        }
+    }
+
+    public void testUpdateWithNullData() {
+        ContentValues  newValues =  new ContentValues();
+        String data = null;
+        newValues.put(MetadataSync.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
+        newValues.put(MetadataSync.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
+        newValues.put(MetadataSync.DATA_SET, TEST_DATA_SET);
+        newValues.put(MetadataSync.RAW_CONTACT_BACKUP_ID, TEST_BACKUP_ID);
+        newValues.put(MetadataSync.DATA, data);
+        newValues.put(MetadataSync.DELETED, 0);
+
+        try {
+            mResolver.update(MetadataSync.CONTENT_URI, newValues, null, null);
+        } catch (IllegalArgumentException e) {
+            // Expected.
+        }
+    }
+
+    public void testUpdateForMetadataSyncId() {
+        Cursor c = mResolver.query(MetadataSync.CONTENT_URI, new String[] {MetadataSync._ID},
+                SELECTION_BY_TEST_ACCOUNT, null, null);
         assertEquals(1, c.getCount());
-        newValues.remove(MetadataSyncColumns.ACCOUNT_ID);
-        c.moveToFirst();
-        assertCursorValues(c, newValues);
+        c.moveToNext();
+        long metadataSyncId = c.getLong(0);
         c.close();
+
+        Uri metadataUri = ContentUris.withAppendedId(MetadataSync.CONTENT_URI, metadataSyncId);
+        ContentValues  newValues =  new ContentValues();
+        String newData = "{\n" +
+                "  \"unique_contact_id\": {\n" +
+                "    \"account_type\": \"CUSTOM_ACCOUNT\",\n" +
+                "    \"custom_account_type\": " + TEST_ACCOUNT_TYPE + ",\n" +
+                "    \"account_name\": " + TEST_ACCOUNT_NAME + ",\n" +
+                "    \"contact_id\": " + TEST_BACKUP_ID + ",\n" +
+                "    \"data_set\": \"GOOGLE_PLUS\"\n" +
+                "  },\n" +
+                "  \"contact_prefs\": {\n" +
+                "    \"send_to_voicemail\": false,\n" +
+                "    \"starred\": false,\n" +
+                "    \"pinned\": 1\n" +
+                "  }\n" +
+                "  }";
+        newValues.put(MetadataSync.DATA, newData);
+        newValues.put(MetadataSync.DELETED, 0);
+        assertEquals(1, mResolver.update(metadataUri, newValues, null, null));
+        assertStoredValue(metadataUri, MetadataSync.DATA, newData);
     }
 
     private void setupData() {
@@ -140,4 +388,4 @@
         defaultValues.put(MetadataSync.DELETED, 0);
         return defaultValues;
     }
-}
+}
\ No newline at end of file