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