Home | History | Annotate | Download | only in contacts
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.contacts;
     18 
     19 import static android.content.ContentProviderOperation.TYPE_ASSERT;
     20 import static android.content.ContentProviderOperation.TYPE_DELETE;
     21 import static android.content.ContentProviderOperation.TYPE_INSERT;
     22 import static android.content.ContentProviderOperation.TYPE_UPDATE;
     23 
     24 import android.content.ContentProviderOperation;
     25 import android.content.ContentValues;
     26 import android.content.Context;
     27 import android.net.Uri;
     28 import android.provider.BaseColumns;
     29 import android.provider.ContactsContract.AggregationExceptions;
     30 import android.provider.ContactsContract.CommonDataKinds.Email;
     31 import android.provider.ContactsContract.CommonDataKinds.Phone;
     32 import android.provider.ContactsContract.Data;
     33 import android.provider.ContactsContract.RawContacts;
     34 import android.test.AndroidTestCase;
     35 import android.test.suitebuilder.annotation.LargeTest;
     36 
     37 import com.android.contacts.RawContactModifierTests.MockContactsSource;
     38 import com.android.contacts.model.RawContact;
     39 import com.android.contacts.model.RawContactDelta;
     40 import com.android.contacts.common.model.ValuesDelta;
     41 import com.android.contacts.model.RawContactDeltaList;
     42 import com.android.contacts.model.RawContactModifier;
     43 import com.android.contacts.common.model.account.AccountType;
     44 import com.google.common.collect.Lists;
     45 
     46 import java.lang.reflect.Field;
     47 import java.util.ArrayList;
     48 import java.util.Collections;
     49 
     50 /**
     51  * Tests for {@link RawContactDeltaList} which focus on "diff" operations that should
     52  * create {@link AggregationExceptions} in certain cases.
     53  */
     54 @LargeTest
     55 public class RawContactDeltaListTests extends AndroidTestCase {
     56     public static final String TAG = RawContactDeltaListTests.class.getSimpleName();
     57 
     58     private static final long CONTACT_FIRST = 1;
     59     private static final long CONTACT_SECOND = 2;
     60 
     61     public static final long CONTACT_BOB = 10;
     62     public static final long CONTACT_MARY = 11;
     63 
     64     public static final long PHONE_RED = 20;
     65     public static final long PHONE_GREEN = 21;
     66     public static final long PHONE_BLUE = 22;
     67 
     68     public static final long EMAIL_YELLOW = 25;
     69 
     70     public static final long VER_FIRST = 100;
     71     public static final long VER_SECOND = 200;
     72 
     73     public static final String TEST_PHONE = "555-1212";
     74     public static final String TEST_ACCOUNT = "org.example.test";
     75 
     76     public RawContactDeltaListTests() {
     77         super();
     78     }
     79 
     80     @Override
     81     public void setUp() {
     82         mContext = getContext();
     83     }
     84 
     85     /**
     86      * Build a {@link AccountType} that has various odd constraints for
     87      * testing purposes.
     88      */
     89     protected AccountType getAccountType() {
     90         return new MockContactsSource();
     91     }
     92 
     93     static ContentValues getValues(ContentProviderOperation operation)
     94             throws NoSuchFieldException, IllegalAccessException {
     95         final Field field = ContentProviderOperation.class.getDeclaredField("mValues");
     96         field.setAccessible(true);
     97         return (ContentValues) field.get(operation);
     98     }
     99 
    100     static RawContactDelta getUpdate(Context context, long rawContactId) {
    101         final RawContact before = RawContactDeltaTests.getRawContact(context, rawContactId,
    102                 RawContactDeltaTests.TEST_PHONE_ID);
    103         return RawContactDelta.fromBefore(before);
    104     }
    105 
    106     static RawContactDelta getInsert() {
    107         final ContentValues after = new ContentValues();
    108         after.put(RawContacts.ACCOUNT_NAME, RawContactDeltaTests.TEST_ACCOUNT_NAME);
    109         after.put(RawContacts.SEND_TO_VOICEMAIL, 1);
    110 
    111         final ValuesDelta values = ValuesDelta.fromAfter(after);
    112         return new RawContactDelta(values);
    113     }
    114 
    115     static RawContactDeltaList buildSet(RawContactDelta... deltas) {
    116         final RawContactDeltaList set = new RawContactDeltaList();
    117         Collections.addAll(set, deltas);
    118         return set;
    119     }
    120 
    121     static RawContactDelta buildBeforeEntity(Context context, long rawContactId, long version,
    122             ContentValues... entries) {
    123         // Build an existing contact read from database
    124         final ContentValues contact = new ContentValues();
    125         contact.put(RawContacts.VERSION, version);
    126         contact.put(RawContacts._ID, rawContactId);
    127         final RawContact before = new RawContact(contact);
    128         for (ContentValues entry : entries) {
    129             before.addDataItemValues(entry);
    130         }
    131         return RawContactDelta.fromBefore(before);
    132     }
    133 
    134     static RawContactDelta buildAfterEntity(ContentValues... entries) {
    135         // Build an existing contact read from database
    136         final ContentValues contact = new ContentValues();
    137         contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT);
    138         final RawContactDelta after = new RawContactDelta(ValuesDelta.fromAfter(contact));
    139         for (ContentValues entry : entries) {
    140             after.addEntry(ValuesDelta.fromAfter(entry));
    141         }
    142         return after;
    143     }
    144 
    145     static ContentValues buildPhone(long phoneId) {
    146         return buildPhone(phoneId, Long.toString(phoneId));
    147     }
    148 
    149     static ContentValues buildPhone(long phoneId, String value) {
    150         final ContentValues values = new ContentValues();
    151         values.put(Data._ID, phoneId);
    152         values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    153         values.put(Phone.NUMBER, value);
    154         values.put(Phone.TYPE, Phone.TYPE_HOME);
    155         return values;
    156     }
    157 
    158     static ContentValues buildEmail(long emailId) {
    159         final ContentValues values = new ContentValues();
    160         values.put(Data._ID, emailId);
    161         values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
    162         values.put(Email.DATA, Long.toString(emailId));
    163         values.put(Email.TYPE, Email.TYPE_HOME);
    164         return values;
    165     }
    166 
    167     static void insertPhone(RawContactDeltaList set, long rawContactId, ContentValues values) {
    168         final RawContactDelta match = set.getByRawContactId(rawContactId);
    169         match.addEntry(ValuesDelta.fromAfter(values));
    170     }
    171 
    172     static ValuesDelta getPhone(RawContactDeltaList set, long rawContactId, long dataId) {
    173         final RawContactDelta match = set.getByRawContactId(rawContactId);
    174         return match.getEntry(dataId);
    175     }
    176 
    177     static void assertDiffPattern(RawContactDelta delta, ContentProviderOperation... pattern) {
    178         final ArrayList<ContentProviderOperation> diff = Lists.newArrayList();
    179         delta.buildAssert(diff);
    180         delta.buildDiff(diff);
    181         assertDiffPattern(diff, pattern);
    182     }
    183 
    184     static void assertDiffPattern(RawContactDeltaList set, ContentProviderOperation... pattern) {
    185         assertDiffPattern(set.buildDiff(), pattern);
    186     }
    187 
    188     static void assertDiffPattern(ArrayList<ContentProviderOperation> diff,
    189             ContentProviderOperation... pattern) {
    190         assertEquals("Unexpected operations", pattern.length, diff.size());
    191         for (int i = 0; i < pattern.length; i++) {
    192             final ContentProviderOperation expected = pattern[i];
    193             final ContentProviderOperation found = diff.get(i);
    194 
    195             assertEquals("Unexpected uri", expected.getUri(), found.getUri());
    196 
    197             final String expectedType = getStringForType(expected.getType());
    198             final String foundType = getStringForType(found.getType());
    199             assertEquals("Unexpected type", expectedType, foundType);
    200 
    201             if (expected.getType() == TYPE_DELETE) continue;
    202 
    203             try {
    204                 final ContentValues expectedValues = getValues(expected);
    205                 final ContentValues foundValues = getValues(found);
    206 
    207                 expectedValues.remove(BaseColumns._ID);
    208                 foundValues.remove(BaseColumns._ID);
    209 
    210                 assertEquals("Unexpected values", expectedValues, foundValues);
    211             } catch (NoSuchFieldException e) {
    212                 fail(e.toString());
    213             } catch (IllegalAccessException e) {
    214                 fail(e.toString());
    215             }
    216         }
    217     }
    218 
    219     static String getStringForType(int type) {
    220         switch (type) {
    221             case TYPE_ASSERT: return "TYPE_ASSERT";
    222             case TYPE_INSERT: return "TYPE_INSERT";
    223             case TYPE_UPDATE: return "TYPE_UPDATE";
    224             case TYPE_DELETE: return "TYPE_DELETE";
    225             default: return Integer.toString(type);
    226         }
    227     }
    228 
    229     static ContentProviderOperation buildAssertVersion(long version) {
    230         final ContentValues values = new ContentValues();
    231         values.put(RawContacts.VERSION, version);
    232         return buildOper(RawContacts.CONTENT_URI, TYPE_ASSERT, values);
    233     }
    234 
    235     static ContentProviderOperation buildAggregationModeUpdate(int mode) {
    236         final ContentValues values = new ContentValues();
    237         values.put(RawContacts.AGGREGATION_MODE, mode);
    238         return buildOper(RawContacts.CONTENT_URI, TYPE_UPDATE, values);
    239     }
    240 
    241     static ContentProviderOperation buildUpdateAggregationSuspended() {
    242         return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED);
    243     }
    244 
    245     static ContentProviderOperation buildUpdateAggregationDefault() {
    246         return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT);
    247     }
    248 
    249     static ContentProviderOperation buildUpdateAggregationKeepTogether(long rawContactId) {
    250         final ContentValues values = new ContentValues();
    251         values.put(AggregationExceptions.RAW_CONTACT_ID1, rawContactId);
    252         values.put(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER);
    253         return buildOper(AggregationExceptions.CONTENT_URI, TYPE_UPDATE, values);
    254     }
    255 
    256     static ContentValues buildDataInsert(ValuesDelta values, long rawContactId) {
    257         final ContentValues insertValues = values.getCompleteValues();
    258         insertValues.put(Data.RAW_CONTACT_ID, rawContactId);
    259         return insertValues;
    260     }
    261 
    262     static ContentProviderOperation buildDelete(Uri uri) {
    263         return buildOper(uri, TYPE_DELETE, (ContentValues)null);
    264     }
    265 
    266     static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) {
    267         return buildOper(uri, type, values.getCompleteValues());
    268     }
    269 
    270     static ContentProviderOperation buildOper(Uri uri, int type, ContentValues values) {
    271         switch (type) {
    272             case TYPE_ASSERT:
    273                 return ContentProviderOperation.newAssertQuery(uri).withValues(values).build();
    274             case TYPE_INSERT:
    275                 return ContentProviderOperation.newInsert(uri).withValues(values).build();
    276             case TYPE_UPDATE:
    277                 return ContentProviderOperation.newUpdate(uri).withValues(values).build();
    278             case TYPE_DELETE:
    279                 return ContentProviderOperation.newDelete(uri).build();
    280         }
    281         return null;
    282     }
    283 
    284     static Long getVersion(RawContactDeltaList set, Long rawContactId) {
    285         return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION);
    286     }
    287 
    288     /**
    289      * Count number of {@link AggregationExceptions} updates contained in the
    290      * given list of {@link ContentProviderOperation}.
    291      */
    292     static int countExceptionUpdates(ArrayList<ContentProviderOperation> diff) {
    293         int updateCount = 0;
    294         for (ContentProviderOperation oper : diff) {
    295             if (AggregationExceptions.CONTENT_URI.equals(oper.getUri())
    296                     && oper.getType() == ContentProviderOperation.TYPE_UPDATE) {
    297                 updateCount++;
    298             }
    299         }
    300         return updateCount;
    301     }
    302 
    303     public void testInsert() {
    304         final RawContactDelta insert = getInsert();
    305         final RawContactDeltaList set = buildSet(insert);
    306 
    307         // Inserting single shouldn't create rules
    308         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
    309         final int exceptionCount = countExceptionUpdates(diff);
    310         assertEquals("Unexpected exception updates", 0, exceptionCount);
    311     }
    312 
    313     public void testUpdateUpdate() {
    314         final RawContactDelta updateFirst = getUpdate(mContext, CONTACT_FIRST);
    315         final RawContactDelta updateSecond = getUpdate(mContext, CONTACT_SECOND);
    316         final RawContactDeltaList set = buildSet(updateFirst, updateSecond);
    317 
    318         // Updating two existing shouldn't create rules
    319         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
    320         final int exceptionCount = countExceptionUpdates(diff);
    321         assertEquals("Unexpected exception updates", 0, exceptionCount);
    322     }
    323 
    324     public void testUpdateInsert() {
    325         final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST);
    326         final RawContactDelta insert = getInsert();
    327         final RawContactDeltaList set = buildSet(update, insert);
    328 
    329         // New insert should only create one rule
    330         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
    331         final int exceptionCount = countExceptionUpdates(diff);
    332         assertEquals("Unexpected exception updates", 1, exceptionCount);
    333     }
    334 
    335     public void testInsertUpdateInsert() {
    336         final RawContactDelta insertFirst = getInsert();
    337         final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST);
    338         final RawContactDelta insertSecond = getInsert();
    339         final RawContactDeltaList set = buildSet(insertFirst, update, insertSecond);
    340 
    341         // Two inserts should create two rules to bind against single existing
    342         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
    343         final int exceptionCount = countExceptionUpdates(diff);
    344         assertEquals("Unexpected exception updates", 2, exceptionCount);
    345     }
    346 
    347     public void testInsertInsertInsert() {
    348         final RawContactDelta insertFirst = getInsert();
    349         final RawContactDelta insertSecond = getInsert();
    350         final RawContactDelta insertThird = getInsert();
    351         final RawContactDeltaList set = buildSet(insertFirst, insertSecond, insertThird);
    352 
    353         // Three new inserts should create only two binding rules
    354         final ArrayList<ContentProviderOperation> diff = set.buildDiff();
    355         final int exceptionCount = countExceptionUpdates(diff);
    356         assertEquals("Unexpected exception updates", 2, exceptionCount);
    357     }
    358 
    359     public void testMergeDataRemoteInsert() {
    360         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    361                 VER_FIRST, buildPhone(PHONE_RED)));
    362         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    363                 VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
    364 
    365         // Merge in second version, verify they match
    366         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    367         assertEquals("Unexpected change when merging", second, merged);
    368     }
    369 
    370     public void testMergeDataLocalUpdateRemoteInsert() {
    371         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    372                 VER_FIRST, buildPhone(PHONE_RED)));
    373         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    374                 VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
    375 
    376         // Change the local number to trigger update
    377         final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
    378         phone.put(Phone.NUMBER, TEST_PHONE);
    379 
    380         assertDiffPattern(first,
    381                 buildAssertVersion(VER_FIRST),
    382                 buildUpdateAggregationSuspended(),
    383                 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
    384                 buildUpdateAggregationDefault());
    385 
    386         // Merge in the second version, verify diff matches
    387         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    388         assertDiffPattern(merged,
    389                 buildAssertVersion(VER_SECOND),
    390                 buildUpdateAggregationSuspended(),
    391                 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
    392                 buildUpdateAggregationDefault());
    393     }
    394 
    395     public void testMergeDataLocalUpdateRemoteDelete() {
    396         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    397                 VER_FIRST, buildPhone(PHONE_RED)));
    398         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    399                 VER_SECOND, buildPhone(PHONE_GREEN)));
    400 
    401         // Change the local number to trigger update
    402         final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
    403         phone.put(Phone.NUMBER, TEST_PHONE);
    404 
    405         assertDiffPattern(first,
    406                 buildAssertVersion(VER_FIRST),
    407                 buildUpdateAggregationSuspended(),
    408                 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
    409                 buildUpdateAggregationDefault());
    410 
    411         // Merge in the second version, verify that our update changed to
    412         // insert, since RED was deleted on remote side
    413         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    414         assertDiffPattern(merged,
    415                 buildAssertVersion(VER_SECOND),
    416                 buildUpdateAggregationSuspended(),
    417                 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(phone, CONTACT_BOB)),
    418                 buildUpdateAggregationDefault());
    419     }
    420 
    421     public void testMergeDataLocalDeleteRemoteUpdate() {
    422         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    423                 VER_FIRST, buildPhone(PHONE_RED)));
    424         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    425                 VER_SECOND, buildPhone(PHONE_RED, TEST_PHONE)));
    426 
    427         // Delete phone locally
    428         final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED);
    429         phone.markDeleted();
    430 
    431         assertDiffPattern(first,
    432                 buildAssertVersion(VER_FIRST),
    433                 buildUpdateAggregationSuspended(),
    434                 buildDelete(Data.CONTENT_URI),
    435                 buildUpdateAggregationDefault());
    436 
    437         // Merge in the second version, verify that our delete remains
    438         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    439         assertDiffPattern(merged,
    440                 buildAssertVersion(VER_SECOND),
    441                 buildUpdateAggregationSuspended(),
    442                 buildDelete(Data.CONTENT_URI),
    443                 buildUpdateAggregationDefault());
    444     }
    445 
    446     public void testMergeDataLocalInsertRemoteInsert() {
    447         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    448                 VER_FIRST, buildPhone(PHONE_RED)));
    449         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    450                 VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN)));
    451 
    452         // Insert new phone locally
    453         final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE));
    454         first.getByRawContactId(CONTACT_BOB).addEntry(bluePhone);
    455         assertDiffPattern(first,
    456                 buildAssertVersion(VER_FIRST),
    457                 buildUpdateAggregationSuspended(),
    458                 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
    459                 buildUpdateAggregationDefault());
    460 
    461         // Merge in the second version, verify that our insert remains
    462         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    463         assertDiffPattern(merged,
    464                 buildAssertVersion(VER_SECOND),
    465                 buildUpdateAggregationSuspended(),
    466                 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)),
    467                 buildUpdateAggregationDefault());
    468     }
    469 
    470     public void testMergeRawContactLocalInsertRemoteInsert() {
    471         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    472                 VER_FIRST, buildPhone(PHONE_RED)));
    473         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    474                 VER_SECOND, buildPhone(PHONE_RED)), buildBeforeEntity(mContext, CONTACT_MARY,
    475                         VER_SECOND, buildPhone(PHONE_RED)));
    476 
    477         // Add new contact locally, should remain insert
    478         final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE);
    479         final RawContactDelta joeContact = buildAfterEntity(joePhoneInsert);
    480         final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues();
    481         joeContactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
    482         first.add(joeContact);
    483         assertDiffPattern(first,
    484                 buildAssertVersion(VER_FIRST),
    485                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
    486                 buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
    487                 buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
    488                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
    489 
    490         // Merge in the second version, verify that our insert remains
    491         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    492         assertDiffPattern(merged,
    493                 buildAssertVersion(VER_SECOND),
    494                 buildAssertVersion(VER_SECOND),
    495                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert),
    496                 buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert),
    497                 buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
    498                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
    499     }
    500 
    501     public void testMergeRawContactLocalDeleteRemoteDelete() {
    502         final RawContactDeltaList first = buildSet(
    503                 buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
    504                 buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
    505         final RawContactDeltaList second = buildSet(
    506                 buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
    507 
    508         // Remove contact locally
    509         first.getByRawContactId(CONTACT_MARY).markDeleted();
    510         assertDiffPattern(first,
    511                 buildAssertVersion(VER_FIRST),
    512                 buildAssertVersion(VER_FIRST),
    513                 buildDelete(RawContacts.CONTENT_URI));
    514 
    515         // Merge in the second version, verify that our delete isn't needed
    516         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    517         assertDiffPattern(merged);
    518     }
    519 
    520     public void testMergeRawContactLocalUpdateRemoteDelete() {
    521         final RawContactDeltaList first = buildSet(
    522                 buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)),
    523                 buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED)));
    524         final RawContactDeltaList second = buildSet(
    525                 buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED)));
    526 
    527         // Perform local update
    528         final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED);
    529         phone.put(Phone.NUMBER, TEST_PHONE);
    530         assertDiffPattern(first,
    531                 buildAssertVersion(VER_FIRST),
    532                 buildAssertVersion(VER_FIRST),
    533                 buildUpdateAggregationSuspended(),
    534                 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()),
    535                 buildUpdateAggregationDefault());
    536 
    537         final ContentValues phoneInsert = phone.getCompleteValues();
    538         final ContentValues contactInsert = first.getByRawContactId(CONTACT_MARY).getValues()
    539                 .getCompleteValues();
    540         contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
    541 
    542         // Merge and verify that update turned into insert
    543         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    544         assertDiffPattern(merged,
    545                 buildAssertVersion(VER_SECOND),
    546                 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert),
    547                 buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert),
    548                 buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT),
    549                 buildUpdateAggregationKeepTogether(CONTACT_BOB));
    550     }
    551 
    552     public void testMergeUsesNewVersion() {
    553         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    554                 VER_FIRST, buildPhone(PHONE_RED)));
    555         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    556                 VER_SECOND, buildPhone(PHONE_RED)));
    557 
    558         assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB));
    559         assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB));
    560 
    561         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    562         assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB));
    563     }
    564 
    565     public void testMergeAfterEnsureAndTrim() {
    566         final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    567                 VER_FIRST, buildEmail(EMAIL_YELLOW)));
    568         final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB,
    569                 VER_SECOND, buildEmail(EMAIL_YELLOW)));
    570 
    571         // Ensure we have at least one phone
    572         final AccountType source = getAccountType();
    573         final RawContactDelta bobContact = first.getByRawContactId(CONTACT_BOB);
    574         RawContactModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE);
    575         final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true);
    576 
    577         // Make sure the update would insert a row
    578         assertDiffPattern(first,
    579                 buildAssertVersion(VER_FIRST),
    580                 buildUpdateAggregationSuspended(),
    581                 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bobPhone, CONTACT_BOB)),
    582                 buildUpdateAggregationDefault());
    583 
    584         // Trim values and ensure that we don't insert things
    585         RawContactModifier.trimEmpty(bobContact, source);
    586         assertDiffPattern(first);
    587 
    588         // Now re-parent the change, which should remain no-op
    589         final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first);
    590         assertDiffPattern(merged);
    591     }
    592 }
    593