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