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