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