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