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