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_DELETE; 20 import static android.content.ContentProviderOperation.TYPE_INSERT; 21 import static android.content.ContentProviderOperation.TYPE_UPDATE; 22 23 import com.android.contacts.model.ContactsSource; 24 import com.android.contacts.model.EntityDelta; 25 import com.android.contacts.model.EntityModifier; 26 import com.android.contacts.model.EntitySet; 27 import com.android.contacts.model.Sources; 28 import com.android.contacts.model.ContactsSource.DataKind; 29 import com.android.contacts.model.ContactsSource.EditType; 30 import com.android.contacts.model.EntityDelta.ValuesDelta; 31 import com.google.android.collect.Lists; 32 33 import android.content.ContentProviderOperation; 34 import android.content.ContentValues; 35 import android.content.Context; 36 import android.content.Entity; 37 import android.os.Bundle; 38 import android.provider.ContactsContract.Intents.Insert; 39 import android.provider.ContactsContract.Data; 40 import android.provider.ContactsContract.RawContacts; 41 import android.provider.ContactsContract.CommonDataKinds.Email; 42 import android.provider.ContactsContract.CommonDataKinds.Im; 43 import android.provider.ContactsContract.CommonDataKinds.Organization; 44 import android.provider.ContactsContract.CommonDataKinds.Phone; 45 import android.provider.ContactsContract.CommonDataKinds.StructuredName; 46 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 47 import android.test.AndroidTestCase; 48 import android.test.suitebuilder.annotation.LargeTest; 49 50 import java.util.ArrayList; 51 import java.util.List; 52 53 /** 54 * Tests for {@link EntityModifier} to verify that {@link ContactsSource} 55 * constraints are being enforced correctly. 56 */ 57 @LargeTest 58 public class EntityModifierTests extends AndroidTestCase { 59 public static final String TAG = "EntityModifierTests"; 60 61 public static final long VER_FIRST = 100; 62 63 private static final long TEST_ID = 4; 64 private static final String TEST_PHONE = "218-555-1212"; 65 private static final String TEST_NAME = "Adam Young"; 66 private static final String TEST_NAME2 = "Breanne Duren"; 67 private static final String TEST_IM = "example (at) example.com"; 68 private static final String TEST_POSTAL = "1600 Amphitheatre Parkway"; 69 70 private static final String TEST_ACCOUNT_NAME = "unittest (at) example.com"; 71 private static final String TEST_ACCOUNT_TYPE = "com.example.unittest"; 72 73 public EntityModifierTests() { 74 super(); 75 } 76 77 @Override 78 public void setUp() { 79 mContext = getContext(); 80 } 81 82 public static class MockContactsSource extends ContactsSource { 83 @Override 84 protected void inflate(Context context, int inflateLevel) { 85 this.accountType = TEST_ACCOUNT_TYPE; 86 this.setInflatedLevel(ContactsSource.LEVEL_CONSTRAINTS); 87 88 // Phone allows maximum 2 home, 1 work, and unlimited other, with 89 // constraint of 5 numbers maximum. 90 DataKind kind = new DataKind(Phone.CONTENT_ITEM_TYPE, -1, -1, 10, true); 91 92 kind.typeOverallMax = 5; 93 kind.typeColumn = Phone.TYPE; 94 kind.typeList = Lists.newArrayList(); 95 kind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2)); 96 kind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1)); 97 kind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true)); 98 kind.typeList.add(new EditType(Phone.TYPE_OTHER, -1)); 99 100 kind.fieldList = Lists.newArrayList(); 101 kind.fieldList.add(new EditField(Phone.NUMBER, -1, -1)); 102 kind.fieldList.add(new EditField(Phone.LABEL, -1, -1)); 103 104 addKind(kind); 105 106 // Email is unlimited 107 kind = new DataKind(Email.CONTENT_ITEM_TYPE, -1, -1, 10, true); 108 kind.typeOverallMax = -1; 109 kind.fieldList = Lists.newArrayList(); 110 kind.fieldList.add(new EditField(Email.DATA, -1, -1)); 111 addKind(kind); 112 113 // IM is only one 114 kind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, -1, 10, true); 115 kind.typeOverallMax = 1; 116 kind.fieldList = Lists.newArrayList(); 117 kind.fieldList.add(new EditField(Im.DATA, -1, -1)); 118 addKind(kind); 119 120 // Organization is only one 121 kind = new DataKind(Organization.CONTENT_ITEM_TYPE, -1, -1, 10, true); 122 kind.typeOverallMax = 1; 123 kind.fieldList = Lists.newArrayList(); 124 kind.fieldList.add(new EditField(Organization.COMPANY, -1, -1)); 125 kind.fieldList.add(new EditField(Organization.TITLE, -1, -1)); 126 addKind(kind); 127 } 128 129 @Override 130 public int getHeaderColor(Context context) { 131 return 0; 132 } 133 134 @Override 135 public int getSideBarColor(Context context) { 136 return 0xffffff; 137 } 138 } 139 140 /** 141 * Build a {@link ContactsSource} that has various odd constraints for 142 * testing purposes. 143 */ 144 protected ContactsSource getSource() { 145 final ContactsSource source = new MockContactsSource(); 146 source.ensureInflated(getContext(), ContactsSource.LEVEL_CONSTRAINTS); 147 return source; 148 } 149 150 /** 151 * Build {@link Sources} instance. 152 */ 153 protected Sources getSources(ContactsSource... sources) { 154 return new Sources(sources); 155 } 156 157 /** 158 * Build an {@link Entity} with the requested set of phone numbers. 159 */ 160 protected EntityDelta getEntity(Long existingId, ContentValues... entries) { 161 final ContentValues contact = new ContentValues(); 162 if (existingId != null) { 163 contact.put(RawContacts._ID, existingId); 164 } 165 contact.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME); 166 contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE); 167 168 final Entity before = new Entity(contact); 169 for (ContentValues values : entries) { 170 before.addSubValue(Data.CONTENT_URI, values); 171 } 172 return EntityDelta.fromBefore(before); 173 } 174 175 /** 176 * Assert this {@link List} contains the given {@link Object}. 177 */ 178 protected void assertContains(List<?> list, Object object) { 179 assertTrue("Missing expected value", list.contains(object)); 180 } 181 182 /** 183 * Assert this {@link List} does not contain the given {@link Object}. 184 */ 185 protected void assertNotContains(List<?> list, Object object) { 186 assertFalse("Contained unexpected value", list.contains(object)); 187 } 188 189 /** 190 * Insert various rows to test 191 * {@link EntityModifier#getValidTypes(EntityDelta, DataKind, EditType)} 192 */ 193 public void testValidTypes() { 194 // Build a source and pull specific types 195 final ContactsSource source = getSource(); 196 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 197 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 198 final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK); 199 final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER); 200 201 List<EditType> validTypes; 202 203 // Add first home, first work 204 final EntityDelta state = getEntity(TEST_ID); 205 EntityModifier.insertChild(state, kindPhone, typeHome); 206 EntityModifier.insertChild(state, kindPhone, typeWork); 207 208 // Expecting home, other 209 validTypes = EntityModifier.getValidTypes(state, kindPhone, null); 210 assertContains(validTypes, typeHome); 211 assertNotContains(validTypes, typeWork); 212 assertContains(validTypes, typeOther); 213 214 // Add second home 215 EntityModifier.insertChild(state, kindPhone, typeHome); 216 217 // Expecting other 218 validTypes = EntityModifier.getValidTypes(state, kindPhone, null); 219 assertNotContains(validTypes, typeHome); 220 assertNotContains(validTypes, typeWork); 221 assertContains(validTypes, typeOther); 222 223 // Add third and fourth home (invalid, but possible) 224 EntityModifier.insertChild(state, kindPhone, typeHome); 225 EntityModifier.insertChild(state, kindPhone, typeHome); 226 227 // Expecting none 228 validTypes = EntityModifier.getValidTypes(state, kindPhone, null); 229 assertNotContains(validTypes, typeHome); 230 assertNotContains(validTypes, typeWork); 231 assertNotContains(validTypes, typeOther); 232 } 233 234 /** 235 * Test {@link EntityModifier#canInsert(EntityDelta, DataKind)} by 236 * inserting various rows. 237 */ 238 public void testCanInsert() { 239 // Build a source and pull specific types 240 final ContactsSource source = getSource(); 241 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 242 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 243 final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK); 244 final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER); 245 246 // Add first home, first work 247 final EntityDelta state = getEntity(TEST_ID); 248 EntityModifier.insertChild(state, kindPhone, typeHome); 249 EntityModifier.insertChild(state, kindPhone, typeWork); 250 assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone)); 251 252 // Add two other, which puts us just under "5" overall limit 253 EntityModifier.insertChild(state, kindPhone, typeOther); 254 EntityModifier.insertChild(state, kindPhone, typeOther); 255 assertTrue("Unable to insert", EntityModifier.canInsert(state, kindPhone)); 256 257 // Add second home, which should push to snug limit 258 EntityModifier.insertChild(state, kindPhone, typeHome); 259 assertFalse("Able to insert", EntityModifier.canInsert(state, kindPhone)); 260 } 261 262 /** 263 * Test 264 * {@link EntityModifier#getBestValidType(EntityDelta, DataKind, boolean, int)} 265 * by asserting expected best options in various states. 266 */ 267 public void testBestValidType() { 268 // Build a source and pull specific types 269 final ContactsSource source = getSource(); 270 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 271 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 272 final EditType typeWork = EntityModifier.getType(kindPhone, Phone.TYPE_WORK); 273 final EditType typeFaxWork = EntityModifier.getType(kindPhone, Phone.TYPE_FAX_WORK); 274 final EditType typeOther = EntityModifier.getType(kindPhone, Phone.TYPE_OTHER); 275 276 EditType suggested; 277 278 // Default suggestion should be home 279 final EntityDelta state = getEntity(TEST_ID); 280 suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); 281 assertEquals("Unexpected suggestion", typeHome, suggested); 282 283 // Add first home, should now suggest work 284 EntityModifier.insertChild(state, kindPhone, typeHome); 285 suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); 286 assertEquals("Unexpected suggestion", typeWork, suggested); 287 288 // Add work fax, should still suggest work 289 EntityModifier.insertChild(state, kindPhone, typeFaxWork); 290 suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); 291 assertEquals("Unexpected suggestion", typeWork, suggested); 292 293 // Add other, should still suggest work 294 EntityModifier.insertChild(state, kindPhone, typeOther); 295 suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); 296 assertEquals("Unexpected suggestion", typeWork, suggested); 297 298 // Add work, now should suggest other 299 EntityModifier.insertChild(state, kindPhone, typeWork); 300 suggested = EntityModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE); 301 assertEquals("Unexpected suggestion", typeOther, suggested); 302 } 303 304 public void testIsEmptyEmpty() { 305 final ContactsSource source = getSource(); 306 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 307 308 // Test entirely empty row 309 final ContentValues after = new ContentValues(); 310 final ValuesDelta values = ValuesDelta.fromAfter(after); 311 312 assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone)); 313 } 314 315 public void testIsEmptyDirectFields() { 316 final ContactsSource source = getSource(); 317 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 318 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 319 320 // Test row that has type values, but core fields are empty 321 final EntityDelta state = getEntity(TEST_ID); 322 final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome); 323 324 assertTrue("Expected empty", EntityModifier.isEmpty(values, kindPhone)); 325 326 // Insert some data to trigger non-empty state 327 values.put(Phone.NUMBER, TEST_PHONE); 328 329 assertFalse("Expected non-empty", EntityModifier.isEmpty(values, kindPhone)); 330 } 331 332 public void testTrimEmptySingle() { 333 final ContactsSource source = getSource(); 334 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 335 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 336 337 // Test row that has type values, but core fields are empty 338 final EntityDelta state = getEntity(TEST_ID); 339 final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome); 340 341 // Build diff, expecting insert for data row and update enforcement 342 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 343 state.buildDiff(diff); 344 assertEquals("Unexpected operations", 3, diff.size()); 345 { 346 final ContentProviderOperation oper = diff.get(0); 347 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 348 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 349 } 350 { 351 final ContentProviderOperation oper = diff.get(1); 352 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 353 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 354 } 355 { 356 final ContentProviderOperation oper = diff.get(2); 357 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 358 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 359 } 360 361 // Trim empty rows and try again, expecting delete of overall contact 362 EntityModifier.trimEmpty(state, source); 363 diff.clear(); 364 state.buildDiff(diff); 365 assertEquals("Unexpected operations", 1, diff.size()); 366 { 367 final ContentProviderOperation oper = diff.get(0); 368 assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); 369 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 370 } 371 } 372 373 public void testTrimEmptySpaces() { 374 final ContactsSource source = getSource(); 375 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 376 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 377 378 // Test row that has type values, but values are spaces 379 final EntityDelta state = EntitySetTests.buildBeforeEntity(TEST_ID, VER_FIRST); 380 final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome); 381 values.put(Phone.NUMBER, " "); 382 383 // Build diff, expecting insert for data row and update enforcement 384 EntitySetTests.assertDiffPattern(state, 385 EntitySetTests.buildAssertVersion(VER_FIRST), 386 EntitySetTests.buildUpdateAggregationSuspended(), 387 EntitySetTests.buildOper(Data.CONTENT_URI, TYPE_INSERT, 388 EntitySetTests.buildDataInsert(values, TEST_ID)), 389 EntitySetTests.buildUpdateAggregationDefault()); 390 391 // Trim empty rows and try again, expecting delete of overall contact 392 EntityModifier.trimEmpty(state, source); 393 EntitySetTests.assertDiffPattern(state, 394 EntitySetTests.buildAssertVersion(VER_FIRST), 395 EntitySetTests.buildDelete(RawContacts.CONTENT_URI)); 396 } 397 398 public void testTrimLeaveValid() { 399 final ContactsSource source = getSource(); 400 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 401 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 402 403 // Test row that has type values with valid number 404 final EntityDelta state = EntitySetTests.buildBeforeEntity(TEST_ID, VER_FIRST); 405 final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome); 406 values.put(Phone.NUMBER, TEST_PHONE); 407 408 // Build diff, expecting insert for data row and update enforcement 409 EntitySetTests.assertDiffPattern(state, 410 EntitySetTests.buildAssertVersion(VER_FIRST), 411 EntitySetTests.buildUpdateAggregationSuspended(), 412 EntitySetTests.buildOper(Data.CONTENT_URI, TYPE_INSERT, 413 EntitySetTests.buildDataInsert(values, TEST_ID)), 414 EntitySetTests.buildUpdateAggregationDefault()); 415 416 // Trim empty rows and try again, expecting no differences 417 EntityModifier.trimEmpty(state, source); 418 EntitySetTests.assertDiffPattern(state, 419 EntitySetTests.buildAssertVersion(VER_FIRST), 420 EntitySetTests.buildUpdateAggregationSuspended(), 421 EntitySetTests.buildOper(Data.CONTENT_URI, TYPE_INSERT, 422 EntitySetTests.buildDataInsert(values, TEST_ID)), 423 EntitySetTests.buildUpdateAggregationDefault()); 424 } 425 426 public void testTrimEmptyUntouched() { 427 final ContactsSource source = getSource(); 428 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 429 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 430 431 // Build "before" that has empty row 432 final EntityDelta state = getEntity(TEST_ID); 433 final ContentValues before = new ContentValues(); 434 before.put(Data._ID, TEST_ID); 435 before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 436 state.addEntry(ValuesDelta.fromBefore(before)); 437 438 // Build diff, expecting no changes 439 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 440 state.buildDiff(diff); 441 assertEquals("Unexpected operations", 0, diff.size()); 442 443 // Try trimming existing empty, which we shouldn't touch 444 EntityModifier.trimEmpty(state, source); 445 diff.clear(); 446 state.buildDiff(diff); 447 assertEquals("Unexpected operations", 0, diff.size()); 448 } 449 450 public void testTrimEmptyAfterUpdate() { 451 final ContactsSource source = getSource(); 452 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 453 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 454 455 // Build "before" that has row with some phone number 456 final ContentValues before = new ContentValues(); 457 before.put(Data._ID, TEST_ID); 458 before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 459 before.put(kindPhone.typeColumn, typeHome.rawValue); 460 before.put(Phone.NUMBER, TEST_PHONE); 461 final EntityDelta state = getEntity(TEST_ID, before); 462 463 // Build diff, expecting no changes 464 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 465 state.buildDiff(diff); 466 assertEquals("Unexpected operations", 0, diff.size()); 467 468 // Now update row by changing number to empty string, expecting single update 469 final ValuesDelta child = state.getEntry(TEST_ID); 470 child.put(Phone.NUMBER, ""); 471 diff.clear(); 472 state.buildDiff(diff); 473 assertEquals("Unexpected operations", 3, diff.size()); 474 { 475 final ContentProviderOperation oper = diff.get(0); 476 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 477 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 478 } 479 { 480 final ContentProviderOperation oper = diff.get(1); 481 assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); 482 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 483 } 484 { 485 final ContentProviderOperation oper = diff.get(2); 486 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 487 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 488 } 489 490 // Now run trim, which should turn that update into delete 491 EntityModifier.trimEmpty(state, source); 492 diff.clear(); 493 state.buildDiff(diff); 494 assertEquals("Unexpected operations", 1, diff.size()); 495 { 496 final ContentProviderOperation oper = diff.get(0); 497 assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); 498 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 499 } 500 } 501 502 public void testTrimInsertEmpty() { 503 final ContactsSource source = getSource(); 504 final Sources sources = getSources(source); 505 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 506 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 507 508 // Try creating a contact without any child entries 509 final EntityDelta state = getEntity(null); 510 final EntitySet set = EntitySet.fromSingle(state); 511 512 // Build diff, expecting single insert 513 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 514 state.buildDiff(diff); 515 assertEquals("Unexpected operations", 2, diff.size()); 516 { 517 final ContentProviderOperation oper = diff.get(0); 518 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 519 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 520 } 521 522 // Trim empty rows and try again, expecting no insert 523 EntityModifier.trimEmpty(set, sources); 524 diff.clear(); 525 state.buildDiff(diff); 526 assertEquals("Unexpected operations", 0, diff.size()); 527 } 528 529 public void testTrimInsertInsert() { 530 final ContactsSource source = getSource(); 531 final Sources sources = getSources(source); 532 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 533 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 534 535 // Try creating a contact with single empty entry 536 final EntityDelta state = getEntity(null); 537 final ValuesDelta values = EntityModifier.insertChild(state, kindPhone, typeHome); 538 final EntitySet set = EntitySet.fromSingle(state); 539 540 // Build diff, expecting two insert operations 541 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 542 state.buildDiff(diff); 543 assertEquals("Unexpected operations", 3, diff.size()); 544 { 545 final ContentProviderOperation oper = diff.get(0); 546 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 547 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 548 } 549 { 550 final ContentProviderOperation oper = diff.get(1); 551 assertEquals("Incorrect type", TYPE_INSERT, oper.getType()); 552 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 553 } 554 555 // Trim empty rows and try again, expecting silence 556 EntityModifier.trimEmpty(set, sources); 557 diff.clear(); 558 state.buildDiff(diff); 559 assertEquals("Unexpected operations", 0, diff.size()); 560 } 561 562 public void testTrimUpdateRemain() { 563 final ContactsSource source = getSource(); 564 final Sources sources = getSources(source); 565 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 566 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 567 568 // Build "before" with two phone numbers 569 final ContentValues first = new ContentValues(); 570 first.put(Data._ID, TEST_ID); 571 first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 572 first.put(kindPhone.typeColumn, typeHome.rawValue); 573 first.put(Phone.NUMBER, TEST_PHONE); 574 575 final ContentValues second = new ContentValues(); 576 second.put(Data._ID, TEST_ID); 577 second.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 578 second.put(kindPhone.typeColumn, typeHome.rawValue); 579 second.put(Phone.NUMBER, TEST_PHONE); 580 581 final EntityDelta state = getEntity(TEST_ID, first, second); 582 final EntitySet set = EntitySet.fromSingle(state); 583 584 // Build diff, expecting no changes 585 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 586 state.buildDiff(diff); 587 assertEquals("Unexpected operations", 0, diff.size()); 588 589 // Now update row by changing number to empty string, expecting single update 590 final ValuesDelta child = state.getEntry(TEST_ID); 591 child.put(Phone.NUMBER, ""); 592 diff.clear(); 593 state.buildDiff(diff); 594 assertEquals("Unexpected operations", 3, diff.size()); 595 { 596 final ContentProviderOperation oper = diff.get(0); 597 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 598 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 599 } 600 { 601 final ContentProviderOperation oper = diff.get(1); 602 assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); 603 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 604 } 605 { 606 final ContentProviderOperation oper = diff.get(2); 607 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 608 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 609 } 610 611 // Now run trim, which should turn that update into delete 612 EntityModifier.trimEmpty(set, sources); 613 diff.clear(); 614 state.buildDiff(diff); 615 assertEquals("Unexpected operations", 3, diff.size()); 616 { 617 final ContentProviderOperation oper = diff.get(0); 618 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 619 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 620 } 621 { 622 final ContentProviderOperation oper = diff.get(1); 623 assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); 624 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 625 } 626 { 627 final ContentProviderOperation oper = diff.get(2); 628 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 629 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 630 } 631 } 632 633 public void testTrimUpdateUpdate() { 634 final ContactsSource source = getSource(); 635 final Sources sources = getSources(source); 636 final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE); 637 final EditType typeHome = EntityModifier.getType(kindPhone, Phone.TYPE_HOME); 638 639 // Build "before" with two phone numbers 640 final ContentValues first = new ContentValues(); 641 first.put(Data._ID, TEST_ID); 642 first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE); 643 first.put(kindPhone.typeColumn, typeHome.rawValue); 644 first.put(Phone.NUMBER, TEST_PHONE); 645 646 final EntityDelta state = getEntity(TEST_ID, first); 647 final EntitySet set = EntitySet.fromSingle(state); 648 649 // Build diff, expecting no changes 650 final ArrayList<ContentProviderOperation> diff = Lists.newArrayList(); 651 state.buildDiff(diff); 652 assertEquals("Unexpected operations", 0, diff.size()); 653 654 // Now update row by changing number to empty string, expecting single update 655 final ValuesDelta child = state.getEntry(TEST_ID); 656 child.put(Phone.NUMBER, ""); 657 diff.clear(); 658 state.buildDiff(diff); 659 assertEquals("Unexpected operations", 3, diff.size()); 660 { 661 final ContentProviderOperation oper = diff.get(0); 662 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 663 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 664 } 665 { 666 final ContentProviderOperation oper = diff.get(1); 667 assertEquals("Incorrect type", TYPE_UPDATE, oper.getType()); 668 assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri()); 669 } 670 { 671 final ContentProviderOperation oper = diff.get(2); 672 assertEquals("Expected aggregation mode change", TYPE_UPDATE, oper.getType()); 673 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 674 } 675 676 // Now run trim, which should turn into deleting the whole contact 677 EntityModifier.trimEmpty(set, sources); 678 diff.clear(); 679 state.buildDiff(diff); 680 assertEquals("Unexpected operations", 1, diff.size()); 681 { 682 final ContentProviderOperation oper = diff.get(0); 683 assertEquals("Incorrect type", TYPE_DELETE, oper.getType()); 684 assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri()); 685 } 686 } 687 688 public void testParseExtrasExistingName() { 689 final ContactsSource source = getSource(); 690 final DataKind kindName = source.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE); 691 692 // Build "before" name 693 final ContentValues first = new ContentValues(); 694 first.put(Data._ID, TEST_ID); 695 first.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE); 696 first.put(StructuredName.GIVEN_NAME, TEST_NAME); 697 698 // Parse extras, making sure we keep single name 699 final EntityDelta state = getEntity(TEST_ID, first); 700 final Bundle extras = new Bundle(); 701 extras.putString(Insert.NAME, TEST_NAME2); 702 EntityModifier.parseExtras(mContext, source, state, extras); 703 704 final int nameCount = state.getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true); 705 assertEquals("Unexpected names", 1, nameCount); 706 } 707 708 public void testParseExtrasIgnoreLimit() { 709 final ContactsSource source = getSource(); 710 final DataKind kindIm = source.getKindForMimetype(Im.CONTENT_ITEM_TYPE); 711 712 // Build "before" IM 713 final ContentValues first = new ContentValues(); 714 first.put(Data._ID, TEST_ID); 715 first.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); 716 first.put(Im.DATA, TEST_IM); 717 718 final EntityDelta state = getEntity(TEST_ID, first); 719 final int beforeCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size(); 720 721 // We should ignore data that doesn't fit source rules, since source 722 // only allows single Im 723 final Bundle extras = new Bundle(); 724 extras.putInt(Insert.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK); 725 extras.putString(Insert.IM_HANDLE, TEST_IM); 726 EntityModifier.parseExtras(mContext, source, state, extras); 727 728 final int afterCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size(); 729 assertEquals("Broke source rules", beforeCount, afterCount); 730 } 731 732 public void testParseExtrasIgnoreUnhandled() { 733 final ContactsSource source = getSource(); 734 final EntityDelta state = getEntity(TEST_ID); 735 736 // We should silently ignore types unsupported by source 737 final Bundle extras = new Bundle(); 738 extras.putString(Insert.POSTAL, TEST_POSTAL); 739 EntityModifier.parseExtras(mContext, source, state, extras); 740 741 assertNull("Broke source rules", state.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE)); 742 } 743 744 public void testParseExtrasJobTitle() { 745 final ContactsSource source = getSource(); 746 final EntityDelta state = getEntity(TEST_ID); 747 748 // Make sure that we create partial Organizations 749 final Bundle extras = new Bundle(); 750 extras.putString(Insert.JOB_TITLE, TEST_NAME); 751 EntityModifier.parseExtras(mContext, source, state, extras); 752 753 final int count = state.getMimeEntries(Organization.CONTENT_ITEM_TYPE).size(); 754 assertEquals("Expected to create organization", 1, count); 755 } 756 } 757