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