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