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