1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.contacts; 18 19 import static android.content.ContentProviderOperation.TYPE_ASSERT; 20 import static android.content.ContentProviderOperation.TYPE_DELETE; 21 import static android.content.ContentProviderOperation.TYPE_INSERT; 22 import static android.content.ContentProviderOperation.TYPE_UPDATE; 23 24 import android.content.ContentProviderOperation; 25 import android.content.ContentValues; 26 import android.content.Context; 27 import android.net.Uri; 28 import android.provider.BaseColumns; 29 import android.provider.ContactsContract.AggregationExceptions; 30 import android.provider.ContactsContract.CommonDataKinds.Email; 31 import android.provider.ContactsContract.CommonDataKinds.Phone; 32 import android.provider.ContactsContract.Data; 33 import android.provider.ContactsContract.RawContacts; 34 import android.test.AndroidTestCase; 35 import android.test.suitebuilder.annotation.LargeTest; 36 37 import com.android.contacts.RawContactModifierTests.MockContactsSource; 38 import com.android.contacts.model.RawContact; 39 import com.android.contacts.model.RawContactDelta; 40 import com.android.contacts.common.model.ValuesDelta; 41 import com.android.contacts.model.RawContactDeltaList; 42 import com.android.contacts.model.RawContactModifier; 43 import com.android.contacts.common.model.account.AccountType; 44 import com.google.common.collect.Lists; 45 46 import java.lang.reflect.Field; 47 import java.util.ArrayList; 48 import java.util.Collections; 49 50 /** 51 * Tests for {@link RawContactDeltaList} which focus on "diff" operations that should 52 * create {@link AggregationExceptions} in certain cases. 53 */ 54 @LargeTest 55 public class RawContactDeltaListTests extends AndroidTestCase { 56 public static final String TAG = RawContactDeltaListTests.class.getSimpleName(); 57 58 private static final long CONTACT_FIRST = 1; 59 private static final long CONTACT_SECOND = 2; 60 61 public static final long CONTACT_BOB = 10; 62 public static final long CONTACT_MARY = 11; 63 64 public static final long PHONE_RED = 20; 65 public static final long PHONE_GREEN = 21; 66 public static final long PHONE_BLUE = 22; 67 68 public static final long EMAIL_YELLOW = 25; 69 70 public static final long VER_FIRST = 100; 71 public static final long VER_SECOND = 200; 72 73 public static final String TEST_PHONE = "555-1212"; 74 public static final String TEST_ACCOUNT = "org.example.test"; 75 76 public RawContactDeltaListTests() { 77 super(); 78 } 79 80 @Override 81 public void setUp() { 82 mContext = getContext(); 83 } 84 85 /** 86 * Build a {@link AccountType} that has various odd constraints for 87 * testing purposes. 88 */ 89 protected AccountType getAccountType() { 90 return new MockContactsSource(); 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 RawContactDelta getUpdate(Context context, long rawContactId) { 101 final RawContact before = RawContactDeltaTests.getRawContact(context, rawContactId, 102 RawContactDeltaTests.TEST_PHONE_ID); 103 return RawContactDelta.fromBefore(before); 104 } 105 106 static RawContactDelta getInsert() { 107 final ContentValues after = new ContentValues(); 108 after.put(RawContacts.ACCOUNT_NAME, RawContactDeltaTests.TEST_ACCOUNT_NAME); 109 after.put(RawContacts.SEND_TO_VOICEMAIL, 1); 110 111 final ValuesDelta values = ValuesDelta.fromAfter(after); 112 return new RawContactDelta(values); 113 } 114 115 static RawContactDeltaList buildSet(RawContactDelta... deltas) { 116 final RawContactDeltaList set = new RawContactDeltaList(); 117 Collections.addAll(set, deltas); 118 return set; 119 } 120 121 static RawContactDelta buildBeforeEntity(Context context, long rawContactId, long version, 122 ContentValues... entries) { 123 // Build an existing contact read from database 124 final ContentValues contact = new ContentValues(); 125 contact.put(RawContacts.VERSION, version); 126 contact.put(RawContacts._ID, rawContactId); 127 final RawContact before = new RawContact(contact); 128 for (ContentValues entry : entries) { 129 before.addDataItemValues(entry); 130 } 131 return RawContactDelta.fromBefore(before); 132 } 133 134 static RawContactDelta buildAfterEntity(ContentValues... entries) { 135 // Build an existing contact read from database 136 final ContentValues contact = new ContentValues(); 137 contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT); 138 final RawContactDelta after = new RawContactDelta(ValuesDelta.fromAfter(contact)); 139 for (ContentValues entry : entries) { 140 after.addEntry(ValuesDelta.fromAfter(entry)); 141 } 142 return after; 143 } 144 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(RawContactDeltaList set, long rawContactId, ContentValues values) { 168 final RawContactDelta match = set.getByRawContactId(rawContactId); 169 match.addEntry(ValuesDelta.fromAfter(values)); 170 } 171 172 static ValuesDelta getPhone(RawContactDeltaList set, long rawContactId, long dataId) { 173 final RawContactDelta match = set.getByRawContactId(rawContactId); 174 return match.getEntry(dataId); 175 } 176 177 static void assertDiffPattern(RawContactDelta delta, ContentProviderOperation... pattern) { 178 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 179 delta.buildAssert(diff); 180 delta.buildDiff(diff); 181 assertDiffPattern(diff, pattern); 182 } 183 184 static void assertDiffPattern(RawContactDeltaList 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(RawContactDeltaList set, Long rawContactId) { 285 return set.getByRawContactId(rawContactId).getValues().getAsLong(RawContacts.VERSION); 286 } 287 288 /** 289 * Count number of {@link AggregationExceptions} updates contained in the 290 * given list of {@link ContentProviderOperation}. 291 */ 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 RawContactDelta insert = getInsert(); 305 final RawContactDeltaList set = buildSet(insert); 306 307 // Inserting single shouldn't create rules 308 final ArrayList<ContentProviderOperation> diff = set.buildDiff(); 309 final int exceptionCount = countExceptionUpdates(diff); 310 assertEquals("Unexpected exception updates", 0, exceptionCount); 311 } 312 313 public void testUpdateUpdate() { 314 final RawContactDelta updateFirst = getUpdate(mContext, CONTACT_FIRST); 315 final RawContactDelta updateSecond = getUpdate(mContext, CONTACT_SECOND); 316 final RawContactDeltaList set = buildSet(updateFirst, updateSecond); 317 318 // Updating two existing shouldn't create rules 319 final ArrayList<ContentProviderOperation> diff = set.buildDiff(); 320 final int exceptionCount = countExceptionUpdates(diff); 321 assertEquals("Unexpected exception updates", 0, exceptionCount); 322 } 323 324 public void testUpdateInsert() { 325 final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST); 326 final RawContactDelta insert = getInsert(); 327 final RawContactDeltaList set = buildSet(update, insert); 328 329 // New insert should only create one rule 330 final ArrayList<ContentProviderOperation> diff = set.buildDiff(); 331 final int exceptionCount = countExceptionUpdates(diff); 332 assertEquals("Unexpected exception updates", 1, exceptionCount); 333 } 334 335 public void testInsertUpdateInsert() { 336 final RawContactDelta insertFirst = getInsert(); 337 final RawContactDelta update = getUpdate(mContext, CONTACT_FIRST); 338 final RawContactDelta insertSecond = getInsert(); 339 final RawContactDeltaList set = buildSet(insertFirst, update, insertSecond); 340 341 // Two inserts should create two rules to bind against single existing 342 final ArrayList<ContentProviderOperation> diff = set.buildDiff(); 343 final int exceptionCount = countExceptionUpdates(diff); 344 assertEquals("Unexpected exception updates", 2, exceptionCount); 345 } 346 347 public void testInsertInsertInsert() { 348 final RawContactDelta insertFirst = getInsert(); 349 final RawContactDelta insertSecond = getInsert(); 350 final RawContactDelta insertThird = getInsert(); 351 final RawContactDeltaList set = buildSet(insertFirst, insertSecond, insertThird); 352 353 // Three new inserts should create only two binding rules 354 final ArrayList<ContentProviderOperation> diff = set.buildDiff(); 355 final int exceptionCount = countExceptionUpdates(diff); 356 assertEquals("Unexpected exception updates", 2, exceptionCount); 357 } 358 359 public void testMergeDataRemoteInsert() { 360 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 361 VER_FIRST, buildPhone(PHONE_RED))); 362 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 363 VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); 364 365 // Merge in second version, verify they match 366 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 367 assertEquals("Unexpected change when merging", second, merged); 368 } 369 370 public void testMergeDataLocalUpdateRemoteInsert() { 371 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 372 VER_FIRST, buildPhone(PHONE_RED))); 373 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 374 VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); 375 376 // Change the local number to trigger update 377 final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED); 378 phone.put(Phone.NUMBER, TEST_PHONE); 379 380 assertDiffPattern(first, 381 buildAssertVersion(VER_FIRST), 382 buildUpdateAggregationSuspended(), 383 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), 384 buildUpdateAggregationDefault()); 385 386 // Merge in the second version, verify diff matches 387 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 388 assertDiffPattern(merged, 389 buildAssertVersion(VER_SECOND), 390 buildUpdateAggregationSuspended(), 391 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), 392 buildUpdateAggregationDefault()); 393 } 394 395 public void testMergeDataLocalUpdateRemoteDelete() { 396 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 397 VER_FIRST, buildPhone(PHONE_RED))); 398 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 399 VER_SECOND, buildPhone(PHONE_GREEN))); 400 401 // Change the local number to trigger update 402 final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED); 403 phone.put(Phone.NUMBER, TEST_PHONE); 404 405 assertDiffPattern(first, 406 buildAssertVersion(VER_FIRST), 407 buildUpdateAggregationSuspended(), 408 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), 409 buildUpdateAggregationDefault()); 410 411 // Merge in the second version, verify that our update changed to 412 // insert, since RED was deleted on remote side 413 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 414 assertDiffPattern(merged, 415 buildAssertVersion(VER_SECOND), 416 buildUpdateAggregationSuspended(), 417 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(phone, CONTACT_BOB)), 418 buildUpdateAggregationDefault()); 419 } 420 421 public void testMergeDataLocalDeleteRemoteUpdate() { 422 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 423 VER_FIRST, buildPhone(PHONE_RED))); 424 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 425 VER_SECOND, buildPhone(PHONE_RED, TEST_PHONE))); 426 427 // Delete phone locally 428 final ValuesDelta phone = getPhone(first, CONTACT_BOB, PHONE_RED); 429 phone.markDeleted(); 430 431 assertDiffPattern(first, 432 buildAssertVersion(VER_FIRST), 433 buildUpdateAggregationSuspended(), 434 buildDelete(Data.CONTENT_URI), 435 buildUpdateAggregationDefault()); 436 437 // Merge in the second version, verify that our delete remains 438 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 439 assertDiffPattern(merged, 440 buildAssertVersion(VER_SECOND), 441 buildUpdateAggregationSuspended(), 442 buildDelete(Data.CONTENT_URI), 443 buildUpdateAggregationDefault()); 444 } 445 446 public void testMergeDataLocalInsertRemoteInsert() { 447 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 448 VER_FIRST, buildPhone(PHONE_RED))); 449 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 450 VER_SECOND, buildPhone(PHONE_RED), buildPhone(PHONE_GREEN))); 451 452 // Insert new phone locally 453 final ValuesDelta bluePhone = ValuesDelta.fromAfter(buildPhone(PHONE_BLUE)); 454 first.getByRawContactId(CONTACT_BOB).addEntry(bluePhone); 455 assertDiffPattern(first, 456 buildAssertVersion(VER_FIRST), 457 buildUpdateAggregationSuspended(), 458 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)), 459 buildUpdateAggregationDefault()); 460 461 // Merge in the second version, verify that our insert remains 462 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 463 assertDiffPattern(merged, 464 buildAssertVersion(VER_SECOND), 465 buildUpdateAggregationSuspended(), 466 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bluePhone, CONTACT_BOB)), 467 buildUpdateAggregationDefault()); 468 } 469 470 public void testMergeRawContactLocalInsertRemoteInsert() { 471 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 472 VER_FIRST, buildPhone(PHONE_RED))); 473 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 474 VER_SECOND, buildPhone(PHONE_RED)), buildBeforeEntity(mContext, CONTACT_MARY, 475 VER_SECOND, buildPhone(PHONE_RED))); 476 477 // Add new contact locally, should remain insert 478 final ContentValues joePhoneInsert = buildPhone(PHONE_BLUE); 479 final RawContactDelta joeContact = buildAfterEntity(joePhoneInsert); 480 final ContentValues joeContactInsert = joeContact.getValues().getCompleteValues(); 481 joeContactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); 482 first.add(joeContact); 483 assertDiffPattern(first, 484 buildAssertVersion(VER_FIRST), 485 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert), 486 buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert), 487 buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT), 488 buildUpdateAggregationKeepTogether(CONTACT_BOB)); 489 490 // Merge in the second version, verify that our insert remains 491 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 492 assertDiffPattern(merged, 493 buildAssertVersion(VER_SECOND), 494 buildAssertVersion(VER_SECOND), 495 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, joeContactInsert), 496 buildOper(Data.CONTENT_URI, TYPE_INSERT, joePhoneInsert), 497 buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT), 498 buildUpdateAggregationKeepTogether(CONTACT_BOB)); 499 } 500 501 public void testMergeRawContactLocalDeleteRemoteDelete() { 502 final RawContactDeltaList first = buildSet( 503 buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)), 504 buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED))); 505 final RawContactDeltaList second = buildSet( 506 buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED))); 507 508 // Remove contact locally 509 first.getByRawContactId(CONTACT_MARY).markDeleted(); 510 assertDiffPattern(first, 511 buildAssertVersion(VER_FIRST), 512 buildAssertVersion(VER_FIRST), 513 buildDelete(RawContacts.CONTENT_URI)); 514 515 // Merge in the second version, verify that our delete isn't needed 516 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 517 assertDiffPattern(merged); 518 } 519 520 public void testMergeRawContactLocalUpdateRemoteDelete() { 521 final RawContactDeltaList first = buildSet( 522 buildBeforeEntity(mContext, CONTACT_BOB, VER_FIRST, buildPhone(PHONE_RED)), 523 buildBeforeEntity(mContext, CONTACT_MARY, VER_FIRST, buildPhone(PHONE_RED))); 524 final RawContactDeltaList second = buildSet( 525 buildBeforeEntity(mContext, CONTACT_BOB, VER_SECOND, buildPhone(PHONE_RED))); 526 527 // Perform local update 528 final ValuesDelta phone = getPhone(first, CONTACT_MARY, PHONE_RED); 529 phone.put(Phone.NUMBER, TEST_PHONE); 530 assertDiffPattern(first, 531 buildAssertVersion(VER_FIRST), 532 buildAssertVersion(VER_FIRST), 533 buildUpdateAggregationSuspended(), 534 buildOper(Data.CONTENT_URI, TYPE_UPDATE, phone.getAfter()), 535 buildUpdateAggregationDefault()); 536 537 final ContentValues phoneInsert = phone.getCompleteValues(); 538 final ContentValues contactInsert = first.getByRawContactId(CONTACT_MARY).getValues() 539 .getCompleteValues(); 540 contactInsert.put(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); 541 542 // Merge and verify that update turned into insert 543 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 544 assertDiffPattern(merged, 545 buildAssertVersion(VER_SECOND), 546 buildOper(RawContacts.CONTENT_URI, TYPE_INSERT, contactInsert), 547 buildOper(Data.CONTENT_URI, TYPE_INSERT, phoneInsert), 548 buildAggregationModeUpdate(RawContacts.AGGREGATION_MODE_DEFAULT), 549 buildUpdateAggregationKeepTogether(CONTACT_BOB)); 550 } 551 552 public void testMergeUsesNewVersion() { 553 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 554 VER_FIRST, buildPhone(PHONE_RED))); 555 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 556 VER_SECOND, buildPhone(PHONE_RED))); 557 558 assertEquals((Long)VER_FIRST, getVersion(first, CONTACT_BOB)); 559 assertEquals((Long)VER_SECOND, getVersion(second, CONTACT_BOB)); 560 561 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 562 assertEquals((Long)VER_SECOND, getVersion(merged, CONTACT_BOB)); 563 } 564 565 public void testMergeAfterEnsureAndTrim() { 566 final RawContactDeltaList first = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 567 VER_FIRST, buildEmail(EMAIL_YELLOW))); 568 final RawContactDeltaList second = buildSet(buildBeforeEntity(mContext, CONTACT_BOB, 569 VER_SECOND, buildEmail(EMAIL_YELLOW))); 570 571 // Ensure we have at least one phone 572 final AccountType source = getAccountType(); 573 final RawContactDelta bobContact = first.getByRawContactId(CONTACT_BOB); 574 RawContactModifier.ensureKindExists(bobContact, source, Phone.CONTENT_ITEM_TYPE); 575 final ValuesDelta bobPhone = bobContact.getSuperPrimaryEntry(Phone.CONTENT_ITEM_TYPE, true); 576 577 // Make sure the update would insert a row 578 assertDiffPattern(first, 579 buildAssertVersion(VER_FIRST), 580 buildUpdateAggregationSuspended(), 581 buildOper(Data.CONTENT_URI, TYPE_INSERT, buildDataInsert(bobPhone, CONTACT_BOB)), 582 buildUpdateAggregationDefault()); 583 584 // Trim values and ensure that we don't insert things 585 RawContactModifier.trimEmpty(bobContact, source); 586 assertDiffPattern(first); 587 588 // Now re-parent the change, which should remain no-op 589 final RawContactDeltaList merged = RawContactDeltaList.mergeAfter(second, first); 590 assertDiffPattern(merged); 591 } 592 } 593