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