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