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.model;
18
19import android.content.ContentProviderOperation;
20import android.content.ContentValues;
21import android.content.Context;
22import android.net.Uri;
23import android.provider.BaseColumns;
24import android.provider.ContactsContract.AggregationExceptions;
25import android.provider.ContactsContract.CommonDataKinds.Email;
26import android.provider.ContactsContract.CommonDataKinds.Phone;
27import android.provider.ContactsContract.Data;
28import android.provider.ContactsContract.RawContacts;
29import android.test.AndroidTestCase;
30import android.test.suitebuilder.annotation.LargeTest;
31
32import com.android.contacts.compat.CompatUtils;
33import com.android.contacts.model.account.AccountType;
34
35import com.google.common.collect.Lists;
36
37import java.lang.reflect.Field;
38import java.util.ArrayList;
39import 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
46public 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