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