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