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 com.android.contacts.EntityModifierTests.MockContactsSource; 25 import com.android.contacts.model.AccountType; 26 import com.android.contacts.model.EntityDelta; 27 import com.android.contacts.model.EntityModifier; 28 import com.android.contacts.model.EntityDeltaList; 29 import com.android.contacts.model.EntityDelta.ValuesDelta; 30 import com.google.android.collect.Lists; 31 32 import android.content.ContentProviderOperation; 33 import android.content.ContentValues; 34 import android.content.Entity; 35 import android.net.Uri; 36 import android.provider.BaseColumns; 37 import android.provider.ContactsContract.AggregationExceptions; 38 import android.provider.ContactsContract.Data; 39 import android.provider.ContactsContract.RawContacts; 40 import android.provider.ContactsContract.CommonDataKinds.Email; 41 import android.provider.ContactsContract.CommonDataKinds.Phone; 42 import android.test.AndroidTestCase; 43 import android.test.suitebuilder.annotation.LargeTest; 44 45 import java.lang.reflect.Field; 46 import java.util.ArrayList; 47 48 /** 49 * Tests for {@link EntityDeltaList} which focus on "diff" operations that should 50 * create {@link AggregationExceptions} in certain cases. 51 */ 52 @LargeTest 53 public class EntityDeltaListTests extends AndroidTestCase { 54 public static final String TAG = "EntityDeltaListTests"; 55 56 private static final long CONTACT_FIRST = 1; 57 private static final long CONTACT_SECOND = 2; 58 59 public static final long CONTACT_BOB = 10; 60 public static final long CONTACT_MARY = 11; 61 62 public static final long PHONE_RED = 20; 63 public static final long PHONE_GREEN = 21; 64 public static final long PHONE_BLUE = 22; 65 66 public static final long EMAIL_YELLOW = 25; 67 68 public static final long VER_FIRST = 100; 69 public static final long VER_SECOND = 200; 70 71 public static final String TEST_PHONE = "555-1212"; 72 public static final String TEST_ACCOUNT = "org.example.test"; 73 74 public EntityDeltaListTests() { 75 super(); 76 } 77 78 @Override 79 public void setUp() { 80 mContext = getContext(); 81 } 82 83 /** 84 * Build a {@link AccountType} that has various odd constraints for 85 * testing purposes. 86 */ 87 protected AccountType getAccountType() { 88 return new MockContactsSource(); 89 } 90 91 static ContentValues getValues(ContentProviderOperation operation) 92 throws NoSuchFieldException, IllegalAccessException { 93 final Field field = ContentProviderOperation.class.getDeclaredField("mValues"); 94 field.setAccessible(true); 95 return (ContentValues) field.get(operation); 96 } 97 98 static EntityDelta getUpdate(long rawContactId) { 99 final Entity before = EntityDeltaTests.getEntity(rawContactId, 100 EntityDeltaTests.TEST_PHONE_ID); 101 return EntityDelta.fromBefore(before); 102 } 103 104 static EntityDelta getInsert() { 105 final ContentValues after = new ContentValues(); 106 after.put(RawContacts.ACCOUNT_NAME, EntityDeltaTests.TEST_ACCOUNT_NAME); 107 after.put(RawContacts.SEND_TO_VOICEMAIL, 1); 108 109 final ValuesDelta values = ValuesDelta.fromAfter(after); 110 return new EntityDelta(values); 111 } 112 113 static EntityDeltaList buildSet(EntityDelta... deltas) { 114 final EntityDeltaList set = EntityDeltaList.fromSingle(deltas[0]); 115 for (int i = 1; i < deltas.length; i++) { 116 set.add(deltas[i]); 117 } 118 return set; 119 } 120 121 static EntityDelta buildBeforeEntity(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 Entity before = new Entity(contact); 128 for (ContentValues entry : entries) { 129 before.addSubValue(Data.CONTENT_URI, entry); 130 } 131 return EntityDelta.fromBefore(before); 132 } 133 134 static EntityDelta 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 EntityDelta after = new EntityDelta(ValuesDelta.fromAfter(contact)); 139 for (ContentValues entry : entries) { 140 after.addEntry(ValuesDelta.fromAfter(entry)); 141 } 142 return after; 143 } 144 145 static ContentValues buildPhone(long phoneId) { 146 return buildPhone(phoneId, Long.toString(phoneId)); 147 } 148 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 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 167 static void insertPhone(EntityDeltaList set, long rawContactId, ContentValues values) { 168 final EntityDelta match = set.getByRawContactId(rawContactId); 169 match.addEntry(ValuesDelta.fromAfter(values)); 170 } 171 172 static ValuesDelta getPhone(EntityDeltaList set, long rawContactId, long dataId) { 173 final EntityDelta match = set.getByRawContactId(rawContactId); 174 return match.getEntry(dataId); 175 } 176 177 static void assertDiffPattern(EntityDelta delta, ContentProviderOperation... pattern) { 178 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 179 delta.buildAssert(diff); 180 delta.buildDiff(diff); 181 assertDiffPattern(diff, pattern); 182 } 183 184 static void assertDiffPattern(EntityDeltaList set, ContentProviderOperation... pattern) { 185 assertDiffPattern(set.buildDiff(), pattern); 186 } 187 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 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 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 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 241 static ContentProviderOperation buildUpdateAggregationSuspended() { 242 return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_SUSPENDED); 243 } 244 245 static ContentProviderOperation buildUpdateAggregationDefault() { 246 return buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT); 247 } 248 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 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 262 static ContentProviderOperation buildDelete(Uri uri) { 263 return buildOper(uri, TYPE_DELETE, (ContentValues)null); 264 } 265 266 static ContentProviderOperation buildOper(Uri uri, int type, ValuesDelta values) { 267 return buildOper(uri, type, values.getCompleteValues()); 268 } 269 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 284 static Long getVersion(EntityDeltaList 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 */ 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 303 public void testInsert() { 304 final EntityDelta insert = getInsert(); 305 final EntityDeltaList 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 313 public void testUpdateUpdate() { 314 final EntityDelta updateFirst = getUpdate(CONTACT_FIRST); 315 final EntityDelta updateSecond = getUpdate(CONTACT_SECOND); 316 final EntityDeltaList 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 324 public void testUpdateInsert() { 325 final EntityDelta update = getUpdate(CONTACT_FIRST); 326 final EntityDelta insert = getInsert(); 327 final EntityDeltaList 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 335 public void testInsertUpdateInsert() { 336 final EntityDelta insertFirst = getInsert(); 337 final EntityDelta update = getUpdate(CONTACT_FIRST); 338 final EntityDelta insertSecond = getInsert(); 339 final EntityDeltaList 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 347 public void testInsertInsertInsert() { 348 final EntityDelta insertFirst = getInsert(); 349 final EntityDelta insertSecond = getInsert(); 350 final EntityDelta insertThird = getInsert(); 351 final EntityDeltaList 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 359 public void testMergeDataRemoteInsert() { 360 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 361 buildPhone(PHONE_RED))); 362 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 363 buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); 364 365 // Merge in second version, verify they match 366 final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first); 367 assertEquals("Unexpected change when merging", second, merged); 368 } 369 370 public void testMergeDataLocalUpdateRemoteInsert() { 371 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 372 buildPhone(PHONE_RED))); 373 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 374 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 EntityDeltaList merged = EntityDeltaList.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 395 public void testMergeDataLocalUpdateRemoteDelete() { 396 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 397 buildPhone(PHONE_RED))); 398 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 399 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 EntityDeltaList merged = EntityDeltaList.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 421 public void testMergeDataLocalDeleteRemoteUpdate() { 422 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 423 buildPhone(PHONE_RED))); 424 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 425 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 EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first); 439 assertDiffPattern(merged, 440 buildAssertVersion(VER_SECOND), 441 buildUpdateAggregationSuspended(), 442 buildDelete(Data.CONTENT_URI), 443 buildUpdateAggregationDefault()); 444 } 445 446 public void testMergeDataLocalInsertRemoteInsert() { 447 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 448 buildPhone(PHONE_RED))); 449 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 450 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 EntityDeltaList merged = EntityDeltaList.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 470 public void testMergeRawContactLocalInsertRemoteInsert() { 471 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 472 buildPhone(PHONE_RED))); 473 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 474 buildPhone(PHONE_RED)), buildBeforeEntity(CONTACT_MARY, VER_SECOND, 475 buildPhone(PHONE_RED))); 476 477 // Add new contact locally, should remain insert 478 final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE); 479 final EntityDelta 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 EntityDeltaList merged = EntityDeltaList.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 501 public void testMergeRawContactLocalDeleteRemoteDelete() { 502 final EntityDeltaList first = buildSet( 503 buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)), 504 buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED))); 505 final EntityDeltaList second = buildSet( 506 buildBeforeEntity(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 EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first); 517 assertDiffPattern(merged); 518 } 519 520 public void testMergeRawContactLocalUpdateRemoteDelete() { 521 final EntityDeltaList first = buildSet( 522 buildBeforeEntity(CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)), 523 buildBeforeEntity(CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED))); 524 final EntityDeltaList second = buildSet( 525 buildBeforeEntity(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 EntityDeltaList merged = EntityDeltaList.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 552 public void testMergeUsesNewVersion() { 553 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 554 buildPhone(PHONE_RED))); 555 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 556 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 EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first); 562 assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB)); 563 } 564 565 public void testMergeAfterEnsureAndTrim() { 566 final EntityDeltaList first = buildSet(buildBeforeEntity(CONTACT_BOB, VER_FIRST, 567 buildEmail(EMAIL_YELLOW))); 568 final EntityDeltaList second = buildSet(buildBeforeEntity(CONTACT_BOB, VER_SECOND, 569 buildEmail(EMAIL_YELLOW))); 570 571 // Ensure we have at least one phone 572 final AccountType source = getAccountType(); 573 final EntityDelta bobContact = first.getByRawContactId(CONTACT_BOB); 574 EntityModifier.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 EntityModifier.trimEmpty(bobContact, source); 586 assertDiffPattern(first); 587 588 // Now re-parent the change, which should remain no-op 589 final EntityDeltaList merged = EntityDeltaList.mergeAfter(second, first); 590 assertDiffPattern(merged); 591 } 592 } 593