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