1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.contacts.model;
18 
19 import android.content.ContentProviderOperation;
20 import android.content.ContentValues;
21 import android.content.Context;
22 import android.net.Uri;
23 import android.provider.BaseColumns;
24 import android.provider.ContactsContract.AggregationExceptions;
25 import android.provider.ContactsContract.CommonDataKinds.Email;
26 import android.provider.ContactsContract.CommonDataKinds.Phone;
27 import android.provider.ContactsContract.Data;
28 import android.provider.ContactsContract.RawContacts;
29 import android.test.AndroidTestCase;
30 import android.test.suitebuilder.annotation.LargeTest;
31 
32 import com.android.contacts.compat.CompatUtils;
33 import com.android.contacts.model.account.AccountType;
34 
35 import com.google.common.collect.Lists;
36 
37 import java.lang.reflect.Field;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 
41 /**
42  * Tests for {@link RawContactDeltaList} which focus on "diff" operations that should
43  * create {@link AggregationExceptions} in certain cases.
44  */
45 @LargeTest
46 public class RawContactDeltaListTests extends AndroidTestCase {
47     // From android.content.ContentProviderOperation
48     public static final int TYPE_INSERT = 1;
49     public static final int TYPE_UPDATE = 2;
50     public static final int TYPE_DELETE = 3;
51     public static final int TYPE_ASSERT = 4;
52 
53     private static final long CONTACT_FIRST = 1;
54     private static final long CONTACT_SECOND = 2;
55 
56     public static final long CONTACT_BOB = 10;
57     public static final long CONTACT_MARY = 11;
58 
59     public static final long PHONE_RED = 20;
60     public static final long PHONE_GREEN = 21;
61     public static final long PHONE_BLUE = 22;
62 
63     public static final long EMAIL_YELLOW = 25;
64 
65     public static final long VER_FIRST = 100;
66     public static final long VER_SECOND = 200;
67 
68     public static final String TEST_PHONE = "555-1212";
69     public static final String TEST_ACCOUNT = "org.example.test";
70 
RawContactDeltaListTests()71     public RawContactDeltaListTests() {
72         super();
73     }
74 
75     @Override
setUp()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      */
getAccountType()84     protected AccountType getAccountType() {
85         return new RawContactModifierTests.MockContactsSource();
86     }
87 
getValues(ContentProviderOperation operation)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 
getUpdate(Context context, long rawContactId)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 
getInsert()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 
buildSet(RawContactDelta... deltas)110     static RawContactDeltaList buildSet(RawContactDelta... deltas) {
111         final RawContactDeltaList set = new RawContactDeltaList();
112         Collections.addAll(set, deltas);
113         return set;
114     }
115 
buildBeforeEntity(Context context, long rawContactId, long version, ContentValues... entries)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 
buildAfterEntity(ContentValues... entries)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 
buildPhone(long phoneId)140     static ContentValues buildPhone(long phoneId) {
141         return buildPhone(phoneId, Long.toString(phoneId));
142     }
143 
buildPhone(long phoneId, String value)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 
buildEmail(long emailId)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 
insertPhone(RawContactDeltaList set, long rawContactId, ContentValues values)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 
getPhone(RawContactDeltaList set, long rawContactId, long dataId)167     static ValuesDelta getPhone(RawContactDeltaList set, long rawContactId, long dataId) {
168         final RawContactDelta match = set.getByRawContactId(rawContactId);
169         return match.getEntry(dataId);
170     }
171 
assertDiffPattern(RawContactDelta delta, CPOWrapper... pattern)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 
assertDiffPattern(RawContactDeltaList set, CPOWrapper... pattern)179     static void assertDiffPattern(RawContactDeltaList set, CPOWrapper... pattern) {
180         assertDiffPattern(set.buildDiffWrapper(), pattern);
181     }
182 
assertDiffPattern(ArrayList<CPOWrapper> diff, CPOWrapper... pattern)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 
getTypeString(CPOWrapper cpoWrapper)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 
buildAssertVersion(long version)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 
buildAggregationModeUpdate(int mode)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 
buildUpdateAggregationSuspended()239     static CPOWrapper buildUpdateAggregationSuspended() {
240         return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED);
241     }
242 
buildUpdateAggregationDefault()243     static CPOWrapper buildUpdateAggregationDefault() {
244         return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT);
245     }
246 
buildUpdateAggregationKeepTogether(long rawContactId)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 
buildDataInsert(ValuesDelta values, long rawContactId)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 
buildDelete(Uri uri)260     static CPOWrapper buildDelete(Uri uri) {
261         return buildCPOWrapper(uri, TYPE_DELETE, (ContentValues) null);
262     }
263 
buildOper(Uri uri, int type, ValuesDelta values)264     static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) {
265         return buildOper(uri, type, values.getCompleteValues());
266     }
267 
buildOper(Uri uri, int type, ContentValues values)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 
buildCPOWrapper(Uri uri, int type, ContentValues values)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 
getVersion(RawContactDeltaList set, Long rawContactId)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      */
countExceptionUpdates(ArrayList<CPOWrapper> diff)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 
testInsert()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 
testUpdateUpdate()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 
testUpdateInsert()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 
testInsertUpdateInsert()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 
testInsertInsertInsert()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 
testMergeDataRemoteInsert()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 
testMergeDataLocalUpdateRemoteInsert()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 
testMergeDataLocalUpdateRemoteDelete()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 
testMergeDataLocalDeleteRemoteUpdate()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 
testMergeDataLocalInsertRemoteInsert()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 
testMergeRawContactLocalInsertRemoteInsert()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 
testMergeRawContactLocalDeleteRemoteDelete()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 
testMergeRawContactLocalUpdateRemoteDelete()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 
testMergeUsesNewVersion()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 
testMergeAfterEnsureAndTrim()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