Home | History | Annotate | Download | only in common
      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.common;
     18 
     19 import android.content.ContentProviderOperation;
     20 import android.content.ContentValues;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.provider.ContactsContract;
     24 import android.provider.ContactsContract.CommonDataKinds.Email;
     25 import android.provider.ContactsContract.CommonDataKinds.Event;
     26 import android.provider.ContactsContract.CommonDataKinds.Im;
     27 import android.provider.ContactsContract.CommonDataKinds.Organization;
     28 import android.provider.ContactsContract.CommonDataKinds.Phone;
     29 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
     30 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
     31 import android.provider.ContactsContract.Data;
     32 import android.provider.ContactsContract.Intents.Insert;
     33 import android.provider.ContactsContract.RawContacts;
     34 import android.test.AndroidTestCase;
     35 import android.test.suitebuilder.annotation.LargeTest;
     36 
     37 import com.android.contacts.common.compat.CompatUtils;
     38 import com.android.contacts.common.model.AccountTypeManager;
     39 import com.android.contacts.common.model.CPOWrapper;
     40 import com.android.contacts.common.model.RawContact;
     41 import com.android.contacts.common.model.RawContactDelta;
     42 import com.android.contacts.common.model.ValuesDelta;
     43 import com.android.contacts.common.model.RawContactDeltaList;
     44 import com.android.contacts.common.model.RawContactModifier;
     45 import com.android.contacts.common.model.account.AccountType;
     46 import com.android.contacts.common.model.account.AccountType.EditType;
     47 import com.android.contacts.common.model.account.ExchangeAccountType;
     48 import com.android.contacts.common.model.account.GoogleAccountType;
     49 import com.android.contacts.common.model.dataitem.DataKind;
     50 import com.android.contacts.common.test.mocks.ContactsMockContext;
     51 import com.android.contacts.common.test.mocks.MockAccountTypeManager;
     52 import com.android.contacts.common.test.mocks.MockContentProvider;
     53 import com.google.common.collect.Lists;
     54 
     55 import java.util.ArrayList;
     56 import java.util.List;
     57 
     58 /**
     59  * Tests for {@link RawContactModifier} to verify that {@link AccountType}
     60  * constraints are being enforced correctly.
     61  */
     62 @LargeTest
     63 public class RawContactModifierTests extends AndroidTestCase {
     64     public static final String TAG = "EntityModifierTests";
     65 
     66     // From android.content.ContentProviderOperation
     67     public static final int TYPE_INSERT = 1;
     68 
     69     public static final long VER_FIRST = 100;
     70 
     71     private static final long TEST_ID = 4;
     72     private static final String TEST_PHONE = "218-555-1212";
     73     private static final String TEST_NAME = "Adam Young";
     74     private static final String TEST_NAME2 = "Breanne Duren";
     75     private static final String TEST_IM = "example (at) example.com";
     76     private static final String TEST_POSTAL = "1600 Amphitheatre Parkway";
     77 
     78     private static final String TEST_ACCOUNT_NAME = "unittest (at) example.com";
     79     private static final String TEST_ACCOUNT_TYPE = "com.example.unittest";
     80 
     81     private static final String EXCHANGE_ACCT_TYPE = "com.android.exchange";
     82 
     83     @Override
     84     public void setUp() {
     85         mContext = getContext();
     86     }
     87 
     88     public static class MockContactsSource extends AccountType {
     89 
     90         MockContactsSource() {
     91             try {
     92                 this.accountType = TEST_ACCOUNT_TYPE;
     93 
     94                 final DataKind nameKind = new DataKind(StructuredName.CONTENT_ITEM_TYPE,
     95                         R.string.nameLabelsGroup, -1, true);
     96                 nameKind.typeOverallMax = 1;
     97                 addKind(nameKind);
     98 
     99                 // Phone allows maximum 2 home, 1 work, and unlimited other, with
    100                 // constraint of 5 numbers maximum.
    101                 final DataKind phoneKind = new DataKind(
    102                         Phone.CONTENT_ITEM_TYPE, -1, 10, true);
    103 
    104                 phoneKind.typeOverallMax = 5;
    105                 phoneKind.typeColumn = Phone.TYPE;
    106                 phoneKind.typeList = Lists.newArrayList();
    107                 phoneKind.typeList.add(new EditType(Phone.TYPE_HOME, -1).setSpecificMax(2));
    108                 phoneKind.typeList.add(new EditType(Phone.TYPE_WORK, -1).setSpecificMax(1));
    109                 phoneKind.typeList.add(new EditType(Phone.TYPE_FAX_WORK, -1).setSecondary(true));
    110                 phoneKind.typeList.add(new EditType(Phone.TYPE_OTHER, -1));
    111 
    112                 phoneKind.fieldList = Lists.newArrayList();
    113                 phoneKind.fieldList.add(new EditField(Phone.NUMBER, -1, -1));
    114                 phoneKind.fieldList.add(new EditField(Phone.LABEL, -1, -1));
    115 
    116                 addKind(phoneKind);
    117 
    118                 // Email is unlimited
    119                 final DataKind emailKind = new DataKind(Email.CONTENT_ITEM_TYPE, -1, 10, true);
    120                 emailKind.typeOverallMax = -1;
    121                 emailKind.fieldList = Lists.newArrayList();
    122                 emailKind.fieldList.add(new EditField(Email.DATA, -1, -1));
    123                 addKind(emailKind);
    124 
    125                 // IM is only one
    126                 final DataKind imKind = new DataKind(Im.CONTENT_ITEM_TYPE, -1, 10, true);
    127                 imKind.typeOverallMax = 1;
    128                 imKind.fieldList = Lists.newArrayList();
    129                 imKind.fieldList.add(new EditField(Im.DATA, -1, -1));
    130                 addKind(imKind);
    131 
    132                 // Organization is only one
    133                 final DataKind orgKind = new DataKind(Organization.CONTENT_ITEM_TYPE, -1, 10, true);
    134                 orgKind.typeOverallMax = 1;
    135                 orgKind.fieldList = Lists.newArrayList();
    136                 orgKind.fieldList.add(new EditField(Organization.COMPANY, -1, -1));
    137                 orgKind.fieldList.add(new EditField(Organization.TITLE, -1, -1));
    138                 addKind(orgKind);
    139             } catch (DefinitionException e) {
    140                 throw new RuntimeException(e);
    141             }
    142         }
    143 
    144         @Override
    145         public boolean isGroupMembershipEditable() {
    146             return false;
    147         }
    148 
    149         @Override
    150         public boolean areContactsWritable() {
    151             return true;
    152         }
    153     }
    154 
    155     /**
    156      * Build a {@link AccountType} that has various odd constraints for
    157      * testing purposes.
    158      */
    159     protected AccountType getAccountType() {
    160         return new MockContactsSource();
    161     }
    162 
    163     /**
    164      * Build {@link AccountTypeManager} instance.
    165      */
    166     protected AccountTypeManager getAccountTypes(AccountType... types) {
    167         return new MockAccountTypeManager(types, null);
    168     }
    169 
    170     /**
    171      * Build an {@link RawContact} with the requested set of phone numbers.
    172      */
    173     protected RawContactDelta getRawContact(Long existingId, ContentValues... entries) {
    174         final ContentValues contact = new ContentValues();
    175         if (existingId != null) {
    176             contact.put(RawContacts._ID, existingId);
    177         }
    178         contact.put(RawContacts.ACCOUNT_NAME, TEST_ACCOUNT_NAME);
    179         contact.put(RawContacts.ACCOUNT_TYPE, TEST_ACCOUNT_TYPE);
    180 
    181         final RawContact before = new RawContact(contact);
    182         for (ContentValues values : entries) {
    183             before.addDataItemValues(values);
    184         }
    185         return RawContactDelta.fromBefore(before);
    186     }
    187 
    188     /**
    189      * Assert this {@link List} contains the given {@link Object}.
    190      */
    191     protected void assertContains(List<?> list, Object object) {
    192         assertTrue("Missing expected value", list.contains(object));
    193     }
    194 
    195     /**
    196      * Assert this {@link List} does not contain the given {@link Object}.
    197      */
    198     protected void assertNotContains(List<?> list, Object object) {
    199         assertFalse("Contained unexpected value", list.contains(object));
    200     }
    201 
    202     /**
    203      * Insert various rows to test
    204      * {@link RawContactModifier#getValidTypes(RawContactDelta, DataKind, EditType)}
    205      */
    206     public void testValidTypes() {
    207         // Build a source and pull specific types
    208         final AccountType source = getAccountType();
    209         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    210         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    211         final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
    212         final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
    213 
    214         List<EditType> validTypes;
    215 
    216         // Add first home, first work
    217         final RawContactDelta state = getRawContact(TEST_ID);
    218         RawContactModifier.insertChild(state, kindPhone, typeHome);
    219         RawContactModifier.insertChild(state, kindPhone, typeWork);
    220 
    221         // Expecting home, other
    222         validTypes = RawContactModifier.getValidTypes(state, kindPhone, null, true, null, true);
    223         assertContains(validTypes, typeHome);
    224         assertNotContains(validTypes, typeWork);
    225         assertContains(validTypes, typeOther);
    226 
    227         // Add second home
    228         RawContactModifier.insertChild(state, kindPhone, typeHome);
    229 
    230         // Expecting other
    231         validTypes = RawContactModifier.getValidTypes(state, kindPhone, null, true, null, true);
    232         assertNotContains(validTypes, typeHome);
    233         assertNotContains(validTypes, typeWork);
    234         assertContains(validTypes, typeOther);
    235 
    236         // Add third and fourth home (invalid, but possible)
    237         RawContactModifier.insertChild(state, kindPhone, typeHome);
    238         RawContactModifier.insertChild(state, kindPhone, typeHome);
    239 
    240         // Expecting none
    241         validTypes = RawContactModifier.getValidTypes(state, kindPhone, null, true, null, true);
    242         assertNotContains(validTypes, typeHome);
    243         assertNotContains(validTypes, typeWork);
    244         assertNotContains(validTypes, typeOther);
    245     }
    246 
    247     /**
    248      * Test which valid types there are when trying to update the editor type.
    249      * {@link RawContactModifier#getValidTypes(RawContactDelta, DataKind, EditType, Boolean)}
    250      */
    251     public void testValidTypesWhenUpdating() {
    252         // Build a source and pull specific types
    253         final AccountType source = getAccountType();
    254         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    255         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    256         final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
    257         final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
    258 
    259         List<EditType> validTypes;
    260 
    261         // Add first home, first work
    262         final RawContactDelta state = getRawContact(TEST_ID);
    263         RawContactModifier.insertChild(state, kindPhone, typeHome);
    264         RawContactModifier.insertChild(state, kindPhone, typeWork);
    265 
    266         // Update editor type for home.
    267         validTypes = RawContactModifier.getValidTypes(state, kindPhone, null, true, null, false);
    268         assertContains(validTypes, typeHome);
    269         assertNotContains(validTypes, typeWork);
    270         assertContains(validTypes, typeOther);
    271 
    272         // Add another 3 types. Overall limit is 5.
    273         RawContactModifier.insertChild(state, kindPhone, typeHome);
    274         RawContactModifier.insertChild(state, kindPhone, typeOther);
    275         RawContactModifier.insertChild(state, kindPhone, typeOther);
    276 
    277         // "Other" is valid when updating the editor type.
    278         validTypes = RawContactModifier.getValidTypes(state, kindPhone, null, true, null, false);
    279         assertNotContains(validTypes, typeHome);
    280         assertNotContains(validTypes, typeWork);
    281         assertContains(validTypes, typeOther);
    282     }
    283 
    284     /**
    285      * Test {@link RawContactModifier#canInsert(RawContactDelta, DataKind)} by
    286      * inserting various rows.
    287      */
    288     public void testCanInsert() {
    289         // Build a source and pull specific types
    290         final AccountType source = getAccountType();
    291         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    292         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    293         final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
    294         final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
    295 
    296         // Add first home, first work
    297         final RawContactDelta state = getRawContact(TEST_ID);
    298         RawContactModifier.insertChild(state, kindPhone, typeHome);
    299         RawContactModifier.insertChild(state, kindPhone, typeWork);
    300         assertTrue("Unable to insert", RawContactModifier.canInsert(state, kindPhone));
    301 
    302         // Add two other, which puts us just under "5" overall limit
    303         RawContactModifier.insertChild(state, kindPhone, typeOther);
    304         RawContactModifier.insertChild(state, kindPhone, typeOther);
    305         assertTrue("Unable to insert", RawContactModifier.canInsert(state, kindPhone));
    306 
    307         // Add second home, which should push to snug limit
    308         RawContactModifier.insertChild(state, kindPhone, typeHome);
    309         assertFalse("Able to insert", RawContactModifier.canInsert(state, kindPhone));
    310     }
    311 
    312     /**
    313      * Test
    314      * {@link RawContactModifier#getBestValidType(RawContactDelta, DataKind, boolean, int)}
    315      * by asserting expected best options in various states.
    316      */
    317     public void testBestValidType() {
    318         // Build a source and pull specific types
    319         final AccountType source = getAccountType();
    320         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    321         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    322         final EditType typeWork = RawContactModifier.getType(kindPhone, Phone.TYPE_WORK);
    323         final EditType typeFaxWork = RawContactModifier.getType(kindPhone, Phone.TYPE_FAX_WORK);
    324         final EditType typeOther = RawContactModifier.getType(kindPhone, Phone.TYPE_OTHER);
    325 
    326         EditType suggested;
    327 
    328         // Default suggestion should be home
    329         final RawContactDelta state = getRawContact(TEST_ID);
    330         suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    331         assertEquals("Unexpected suggestion", typeHome, suggested);
    332 
    333         // Add first home, should now suggest work
    334         RawContactModifier.insertChild(state, kindPhone, typeHome);
    335         suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    336         assertEquals("Unexpected suggestion", typeWork, suggested);
    337 
    338         // Add work fax, should still suggest work
    339         RawContactModifier.insertChild(state, kindPhone, typeFaxWork);
    340         suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    341         assertEquals("Unexpected suggestion", typeWork, suggested);
    342 
    343         // Add other, should still suggest work
    344         RawContactModifier.insertChild(state, kindPhone, typeOther);
    345         suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    346         assertEquals("Unexpected suggestion", typeWork, suggested);
    347 
    348         // Add work, now should suggest other
    349         RawContactModifier.insertChild(state, kindPhone, typeWork);
    350         suggested = RawContactModifier.getBestValidType(state, kindPhone, false, Integer.MIN_VALUE);
    351         assertEquals("Unexpected suggestion", typeOther, suggested);
    352     }
    353 
    354     public void testIsEmptyEmpty() {
    355         final AccountType source = getAccountType();
    356         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    357 
    358         // Test entirely empty row
    359         final ContentValues after = new ContentValues();
    360         final ValuesDelta values = ValuesDelta.fromAfter(after);
    361 
    362         assertTrue("Expected empty", RawContactModifier.isEmpty(values, kindPhone));
    363     }
    364 
    365     public void testIsEmptyDirectFields() {
    366         final AccountType source = getAccountType();
    367         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    368         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    369 
    370         // Test row that has type values, but core fields are empty
    371         final RawContactDelta state = getRawContact(TEST_ID);
    372         final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome);
    373 
    374         assertTrue("Expected empty", RawContactModifier.isEmpty(values, kindPhone));
    375 
    376         // Insert some data to trigger non-empty state
    377         values.put(Phone.NUMBER, TEST_PHONE);
    378 
    379         assertFalse("Expected non-empty", RawContactModifier.isEmpty(values, kindPhone));
    380     }
    381 
    382     public void testTrimEmptySingle() {
    383         final AccountType source = getAccountType();
    384         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    385         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    386 
    387         // Test row that has type values, but core fields are empty
    388         final RawContactDelta state = getRawContact(TEST_ID);
    389         RawContactModifier.insertChild(state, kindPhone, typeHome);
    390 
    391         // Build diff, expecting insert for data row and update enforcement
    392         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    393         state.buildDiffWrapper(diff);
    394         assertEquals("Unexpected operations", 3, diff.size());
    395         {
    396             final CPOWrapper cpoWrapper = diff.get(0);
    397             final ContentProviderOperation oper = cpoWrapper.getOperation();
    398             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    399             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    400         }
    401         {
    402             final CPOWrapper cpoWrapper = diff.get(1);
    403             final ContentProviderOperation oper = cpoWrapper.getOperation();
    404             assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper));
    405             assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    406         }
    407         {
    408             final CPOWrapper cpoWrapper = diff.get(2);
    409             final ContentProviderOperation oper = cpoWrapper.getOperation();
    410             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    411             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    412         }
    413 
    414         // Trim empty rows and try again, expecting delete of overall contact
    415         RawContactModifier.trimEmpty(state, source);
    416         diff.clear();
    417         state.buildDiffWrapper(diff);
    418         assertEquals("Unexpected operations", 1, diff.size());
    419         {
    420             final CPOWrapper cpoWrapper = diff.get(0);
    421             final ContentProviderOperation oper = cpoWrapper.getOperation();
    422             assertTrue("Incorrect type", CompatUtils.isDeleteCompat(cpoWrapper));
    423             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    424         }
    425     }
    426 
    427     public void testTrimEmptySpaces() {
    428         final AccountType source = getAccountType();
    429         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    430         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    431 
    432         // Test row that has type values, but values are spaces
    433         final RawContactDelta state = RawContactDeltaListTests.buildBeforeEntity(mContext, TEST_ID,
    434                 VER_FIRST);
    435         final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome);
    436         values.put(Phone.NUMBER, "   ");
    437 
    438         // Build diff, expecting insert for data row and update enforcement
    439         RawContactDeltaListTests.assertDiffPattern(state,
    440                 RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
    441                 RawContactDeltaListTests.buildUpdateAggregationSuspended(),
    442                 RawContactDeltaListTests.buildCPOWrapper(Data.CONTENT_URI, TYPE_INSERT,
    443                         RawContactDeltaListTests.buildDataInsert(values, TEST_ID)),
    444                 RawContactDeltaListTests.buildUpdateAggregationDefault());
    445 
    446         // Trim empty rows and try again, expecting delete of overall contact
    447         RawContactModifier.trimEmpty(state, source);
    448         RawContactDeltaListTests.assertDiffPattern(state,
    449                 RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
    450                 RawContactDeltaListTests.buildDelete(RawContacts.CONTENT_URI));
    451     }
    452 
    453     public void testTrimLeaveValid() {
    454         final AccountType source = getAccountType();
    455         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    456         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    457 
    458         // Test row that has type values with valid number
    459         final RawContactDelta state = RawContactDeltaListTests.buildBeforeEntity(mContext, TEST_ID,
    460                 VER_FIRST);
    461         final ValuesDelta values = RawContactModifier.insertChild(state, kindPhone, typeHome);
    462         values.put(Phone.NUMBER, TEST_PHONE);
    463 
    464         // Build diff, expecting insert for data row and update enforcement
    465         RawContactDeltaListTests.assertDiffPattern(state,
    466                 RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
    467                 RawContactDeltaListTests.buildUpdateAggregationSuspended(),
    468                 RawContactDeltaListTests.buildCPOWrapper(Data.CONTENT_URI, TYPE_INSERT,
    469                         RawContactDeltaListTests.buildDataInsert(values, TEST_ID)),
    470                 RawContactDeltaListTests.buildUpdateAggregationDefault());
    471 
    472         // Trim empty rows and try again, expecting no differences
    473         RawContactModifier.trimEmpty(state, source);
    474         RawContactDeltaListTests.assertDiffPattern(state,
    475                 RawContactDeltaListTests.buildAssertVersion(VER_FIRST),
    476                 RawContactDeltaListTests.buildUpdateAggregationSuspended(),
    477                 RawContactDeltaListTests.buildCPOWrapper(Data.CONTENT_URI, TYPE_INSERT,
    478                         RawContactDeltaListTests.buildDataInsert(values, TEST_ID)),
    479                 RawContactDeltaListTests.buildUpdateAggregationDefault());
    480     }
    481 
    482     public void testTrimEmptyUntouched() {
    483         final AccountType source = getAccountType();
    484         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    485         RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    486 
    487         // Build "before" that has empty row
    488         final RawContactDelta state = getRawContact(TEST_ID);
    489         final ContentValues before = new ContentValues();
    490         before.put(Data._ID, TEST_ID);
    491         before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    492         state.addEntry(ValuesDelta.fromBefore(before));
    493 
    494         // Build diff, expecting no changes
    495         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    496         state.buildDiffWrapper(diff);
    497         assertEquals("Unexpected operations", 0, diff.size());
    498 
    499         // Try trimming existing empty, which we shouldn't touch
    500         RawContactModifier.trimEmpty(state, source);
    501         diff.clear();
    502         state.buildDiffWrapper(diff);
    503         assertEquals("Unexpected operations", 0, diff.size());
    504     }
    505 
    506     public void testTrimEmptyAfterUpdate() {
    507         final AccountType source = getAccountType();
    508         final DataKind kindPhone = source.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    509         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    510 
    511         // Build "before" that has row with some phone number
    512         final ContentValues before = new ContentValues();
    513         before.put(Data._ID, TEST_ID);
    514         before.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    515         before.put(kindPhone.typeColumn, typeHome.rawValue);
    516         before.put(Phone.NUMBER, TEST_PHONE);
    517         final RawContactDelta state = getRawContact(TEST_ID, before);
    518 
    519         // Build diff, expecting no changes
    520         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    521         state.buildDiffWrapper(diff);
    522         assertEquals("Unexpected operations", 0, diff.size());
    523 
    524         // Now update row by changing number to empty string, expecting single update
    525         final ValuesDelta child = state.getEntry(TEST_ID);
    526         child.put(Phone.NUMBER, "");
    527         diff.clear();
    528         state.buildDiffWrapper(diff);
    529         assertEquals("Unexpected operations", 3, diff.size());
    530         {
    531             final CPOWrapper cpoWrapper = diff.get(0);
    532             final ContentProviderOperation oper = cpoWrapper.getOperation();
    533             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    534             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    535         }
    536         {
    537             final CPOWrapper cpoWrapper = diff.get(1);
    538             final ContentProviderOperation oper = cpoWrapper.getOperation();
    539             assertTrue("Incorrect type", CompatUtils.isUpdateCompat(cpoWrapper));
    540             assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    541         }
    542         {
    543             final CPOWrapper cpoWrapper = diff.get(2);
    544             final ContentProviderOperation oper = cpoWrapper.getOperation();
    545             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    546             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    547         }
    548 
    549         // Now run trim, which should turn that update into delete
    550         RawContactModifier.trimEmpty(state, source);
    551         diff.clear();
    552         state.buildDiffWrapper(diff);
    553         assertEquals("Unexpected operations", 1, diff.size());
    554         {
    555             final CPOWrapper cpoWrapper = diff.get(0);
    556             final ContentProviderOperation oper = cpoWrapper.getOperation();
    557             assertTrue("Incorrect type", CompatUtils.isDeleteCompat(cpoWrapper));
    558             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    559         }
    560     }
    561 
    562     public void testTrimInsertEmpty() {
    563         final AccountType accountType = getAccountType();
    564         final AccountTypeManager accountTypes = getAccountTypes(accountType);
    565         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    566         RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    567 
    568         // Try creating a contact without any child entries
    569         final RawContactDelta state = getRawContact(null);
    570         final RawContactDeltaList set = new RawContactDeltaList();
    571         set.add(state);
    572 
    573         // Build diff, expecting single insert
    574         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    575         state.buildDiffWrapper(diff);
    576         assertEquals("Unexpected operations", 2, diff.size());
    577         {
    578             final CPOWrapper cpoWrapper = diff.get(0);
    579             final ContentProviderOperation oper = cpoWrapper.getOperation();
    580             assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper));
    581             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    582         }
    583 
    584         // Trim empty rows and try again, expecting no insert
    585         RawContactModifier.trimEmpty(set, accountTypes);
    586         diff.clear();
    587         state.buildDiffWrapper(diff);
    588         assertEquals("Unexpected operations", 0, diff.size());
    589     }
    590 
    591     public void testTrimInsertInsert() {
    592         final AccountType accountType = getAccountType();
    593         final AccountTypeManager accountTypes = getAccountTypes(accountType);
    594         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    595         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    596 
    597         // Try creating a contact with single empty entry
    598         final RawContactDelta state = getRawContact(null);
    599         RawContactModifier.insertChild(state, kindPhone, typeHome);
    600         final RawContactDeltaList set = new RawContactDeltaList();
    601         set.add(state);
    602 
    603         // Build diff, expecting two insert operations
    604         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    605         state.buildDiffWrapper(diff);
    606         assertEquals("Unexpected operations", 3, diff.size());
    607         {
    608             final CPOWrapper cpoWrapper = diff.get(0);
    609             final ContentProviderOperation oper = cpoWrapper.getOperation();
    610             assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper));
    611             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    612         }
    613         {
    614             final CPOWrapper cpoWrapper = diff.get(1);
    615             final ContentProviderOperation oper = cpoWrapper.getOperation();
    616             assertTrue("Incorrect type", CompatUtils.isInsertCompat(cpoWrapper));
    617             assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    618         }
    619 
    620         // Trim empty rows and try again, expecting silence
    621         RawContactModifier.trimEmpty(set, accountTypes);
    622         diff.clear();
    623         state.buildDiffWrapper(diff);
    624         assertEquals("Unexpected operations", 0, diff.size());
    625     }
    626 
    627     public void testTrimUpdateRemain() {
    628         final AccountType accountType = getAccountType();
    629         final AccountTypeManager accountTypes = getAccountTypes(accountType);
    630         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    631         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    632 
    633         // Build "before" with two phone numbers
    634         final ContentValues first = new ContentValues();
    635         first.put(Data._ID, TEST_ID);
    636         first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    637         first.put(kindPhone.typeColumn, typeHome.rawValue);
    638         first.put(Phone.NUMBER, TEST_PHONE);
    639 
    640         final ContentValues second = new ContentValues();
    641         second.put(Data._ID, TEST_ID);
    642         second.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    643         second.put(kindPhone.typeColumn, typeHome.rawValue);
    644         second.put(Phone.NUMBER, TEST_PHONE);
    645 
    646         final RawContactDelta state = getRawContact(TEST_ID, first, second);
    647         final RawContactDeltaList set = new RawContactDeltaList();
    648         set.add(state);
    649 
    650         // Build diff, expecting no changes
    651         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    652         state.buildDiffWrapper(diff);
    653         assertEquals("Unexpected operations", 0, diff.size());
    654 
    655         // Now update row by changing number to empty string, expecting single update
    656         final ValuesDelta child = state.getEntry(TEST_ID);
    657         child.put(Phone.NUMBER, "");
    658         diff.clear();
    659         state.buildDiffWrapper(diff);
    660         assertEquals("Unexpected operations", 3, diff.size());
    661         {
    662             final CPOWrapper cpoWrapper = diff.get(0);
    663             final ContentProviderOperation oper = cpoWrapper.getOperation();
    664             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    665             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    666         }
    667         {
    668             final CPOWrapper cpoWrapper = diff.get(1);
    669             final ContentProviderOperation oper = cpoWrapper.getOperation();
    670             assertTrue("Incorrect type", CompatUtils.isUpdateCompat(cpoWrapper));
    671             assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    672         }
    673         {
    674             final CPOWrapper cpoWrapper = diff.get(2);
    675             final ContentProviderOperation oper = cpoWrapper.getOperation();
    676             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    677             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    678         }
    679 
    680         // Now run trim, which should turn that update into delete
    681         RawContactModifier.trimEmpty(set, accountTypes);
    682         diff.clear();
    683         state.buildDiffWrapper(diff);
    684         assertEquals("Unexpected operations", 3, diff.size());
    685         {
    686             final CPOWrapper cpoWrapper = diff.get(0);
    687             final ContentProviderOperation oper = cpoWrapper.getOperation();
    688             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    689             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    690         }
    691         {
    692             final CPOWrapper cpoWrapper = diff.get(1);
    693             final ContentProviderOperation oper = cpoWrapper.getOperation();
    694             assertTrue("Incorrect type", CompatUtils.isDeleteCompat(cpoWrapper));
    695             assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    696         }
    697         {
    698             final CPOWrapper cpoWrapper = diff.get(2);
    699             final ContentProviderOperation oper = cpoWrapper.getOperation();
    700             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    701             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    702         }
    703     }
    704 
    705     public void testTrimUpdateUpdate() {
    706         final AccountType accountType = getAccountType();
    707         final AccountTypeManager accountTypes = getAccountTypes(accountType);
    708         final DataKind kindPhone = accountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
    709         final EditType typeHome = RawContactModifier.getType(kindPhone, Phone.TYPE_HOME);
    710 
    711         // Build "before" with two phone numbers
    712         final ContentValues first = new ContentValues();
    713         first.put(Data._ID, TEST_ID);
    714         first.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    715         first.put(kindPhone.typeColumn, typeHome.rawValue);
    716         first.put(Phone.NUMBER, TEST_PHONE);
    717 
    718         final RawContactDelta state = getRawContact(TEST_ID, first);
    719         final RawContactDeltaList set = new RawContactDeltaList();
    720         set.add(state);
    721 
    722         // Build diff, expecting no changes
    723         final ArrayList<CPOWrapper> diff = Lists.newArrayList();
    724         state.buildDiffWrapper(diff);
    725         assertEquals("Unexpected operations", 0, diff.size());
    726 
    727         // Now update row by changing number to empty string, expecting single update
    728         final ValuesDelta child = state.getEntry(TEST_ID);
    729         child.put(Phone.NUMBER, "");
    730         diff.clear();
    731         state.buildDiffWrapper(diff);
    732         assertEquals("Unexpected operations", 3, diff.size());
    733         {
    734             final CPOWrapper cpoWrapper = diff.get(0);
    735             final ContentProviderOperation oper = cpoWrapper.getOperation();
    736             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    737             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    738         }
    739         {
    740             final CPOWrapper cpoWrapper = diff.get(1);
    741             final ContentProviderOperation oper = cpoWrapper.getOperation();
    742             assertTrue("Incorrect type", CompatUtils.isUpdateCompat(cpoWrapper));
    743             assertEquals("Incorrect target", Data.CONTENT_URI, oper.getUri());
    744         }
    745         {
    746             final CPOWrapper cpoWrapper = diff.get(2);
    747             final ContentProviderOperation oper = cpoWrapper.getOperation();
    748             assertTrue("Expected aggregation mode change", CompatUtils.isUpdateCompat(cpoWrapper));
    749             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    750         }
    751 
    752         // Now run trim, which should turn into deleting the whole contact
    753         RawContactModifier.trimEmpty(set, accountTypes);
    754         diff.clear();
    755         state.buildDiffWrapper(diff);
    756         assertEquals("Unexpected operations", 1, diff.size());
    757         {
    758             final CPOWrapper cpoWrapper = diff.get(0);
    759             final ContentProviderOperation oper = cpoWrapper.getOperation();
    760             assertTrue("Incorrect type", CompatUtils.isDeleteCompat(cpoWrapper));
    761             assertEquals("Incorrect target", RawContacts.CONTENT_URI, oper.getUri());
    762         }
    763     }
    764 
    765     public void testParseExtrasExistingName() {
    766         final AccountType accountType = getAccountType();
    767 
    768         // Build "before" name
    769         final ContentValues first = new ContentValues();
    770         first.put(Data._ID, TEST_ID);
    771         first.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    772         first.put(StructuredName.GIVEN_NAME, TEST_NAME);
    773 
    774         // Parse extras, making sure we keep single name
    775         final RawContactDelta state = getRawContact(TEST_ID, first);
    776         final Bundle extras = new Bundle();
    777         extras.putString(Insert.NAME, TEST_NAME2);
    778         RawContactModifier.parseExtras(mContext, accountType, state, extras);
    779 
    780         final int nameCount = state.getMimeEntriesCount(StructuredName.CONTENT_ITEM_TYPE, true);
    781         assertEquals("Unexpected names", 1, nameCount);
    782     }
    783 
    784     public void testParseExtrasIgnoreLimit() {
    785         final AccountType accountType = getAccountType();
    786 
    787         // Build "before" IM
    788         final ContentValues first = new ContentValues();
    789         first.put(Data._ID, TEST_ID);
    790         first.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
    791         first.put(Im.DATA, TEST_IM);
    792 
    793         final RawContactDelta state = getRawContact(TEST_ID, first);
    794         final int beforeCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
    795 
    796         // We should ignore data that doesn't fit account type rules, since account type
    797         // only allows single Im
    798         final Bundle extras = new Bundle();
    799         extras.putInt(Insert.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
    800         extras.putString(Insert.IM_HANDLE, TEST_IM);
    801         RawContactModifier.parseExtras(mContext, accountType, state, extras);
    802 
    803         final int afterCount = state.getMimeEntries(Im.CONTENT_ITEM_TYPE).size();
    804         assertEquals("Broke account type rules", beforeCount, afterCount);
    805     }
    806 
    807     public void testParseExtrasIgnoreUnhandled() {
    808         final AccountType accountType = getAccountType();
    809         final RawContactDelta state = getRawContact(TEST_ID);
    810 
    811         // We should silently ignore types unsupported by account type
    812         final Bundle extras = new Bundle();
    813         extras.putString(Insert.POSTAL, TEST_POSTAL);
    814         RawContactModifier.parseExtras(mContext, accountType, state, extras);
    815 
    816         assertNull("Broke accoun type rules",
    817                 state.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE));
    818     }
    819 
    820     public void testParseExtrasJobTitle() {
    821         final AccountType accountType = getAccountType();
    822         final RawContactDelta state = getRawContact(TEST_ID);
    823 
    824         // Make sure that we create partial Organizations
    825         final Bundle extras = new Bundle();
    826         extras.putString(Insert.JOB_TITLE, TEST_NAME);
    827         RawContactModifier.parseExtras(mContext, accountType, state, extras);
    828 
    829         final int count = state.getMimeEntries(Organization.CONTENT_ITEM_TYPE).size();
    830         assertEquals("Expected to create organization", 1, count);
    831     }
    832 
    833     public void testMigrateWithDisplayNameFromGoogleToExchange1() {
    834         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
    835         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
    836         DataKind kind = newAccountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
    837 
    838         ContactsMockContext context = new ContactsMockContext(getContext());
    839 
    840         RawContactDelta oldState = new RawContactDelta();
    841         ContentValues mockNameValues = new ContentValues();
    842         mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    843         mockNameValues.put(StructuredName.PREFIX, "prefix");
    844         mockNameValues.put(StructuredName.GIVEN_NAME, "given");
    845         mockNameValues.put(StructuredName.MIDDLE_NAME, "middle");
    846         mockNameValues.put(StructuredName.FAMILY_NAME, "family");
    847         mockNameValues.put(StructuredName.SUFFIX, "suffix");
    848         mockNameValues.put(StructuredName.PHONETIC_FAMILY_NAME, "PHONETIC_FAMILY");
    849         mockNameValues.put(StructuredName.PHONETIC_MIDDLE_NAME, "PHONETIC_MIDDLE");
    850         mockNameValues.put(StructuredName.PHONETIC_GIVEN_NAME, "PHONETIC_GIVEN");
    851         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
    852 
    853         RawContactDelta newState = new RawContactDelta();
    854         RawContactModifier.migrateStructuredName(context, oldState, newState, kind);
    855         List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
    856         assertEquals(1, list.size());
    857 
    858         ContentValues output = list.get(0).getAfter();
    859         assertEquals("prefix", output.getAsString(StructuredName.PREFIX));
    860         assertEquals("given", output.getAsString(StructuredName.GIVEN_NAME));
    861         assertEquals("middle", output.getAsString(StructuredName.MIDDLE_NAME));
    862         assertEquals("family", output.getAsString(StructuredName.FAMILY_NAME));
    863         assertEquals("suffix", output.getAsString(StructuredName.SUFFIX));
    864         // Phonetic middle name isn't supported by Exchange.
    865         assertEquals("PHONETIC_FAMILY", output.getAsString(StructuredName.PHONETIC_FAMILY_NAME));
    866         assertEquals("PHONETIC_GIVEN", output.getAsString(StructuredName.PHONETIC_GIVEN_NAME));
    867     }
    868 
    869     public void testMigrateWithDisplayNameFromGoogleToExchange2() {
    870         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
    871         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
    872         DataKind kind = newAccountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
    873 
    874         ContactsMockContext context = new ContactsMockContext(getContext());
    875         MockContentProvider provider = context.getContactsProvider();
    876 
    877         String inputDisplayName = "prefix given middle family suffix";
    878         // The method will ask the provider to split/join StructuredName.
    879         Uri uriForBuildDisplayName =
    880                 ContactsContract.AUTHORITY_URI
    881                         .buildUpon()
    882                         .appendPath("complete_name")
    883                         .appendQueryParameter(StructuredName.DISPLAY_NAME, inputDisplayName)
    884                         .build();
    885         provider.expectQuery(uriForBuildDisplayName)
    886                 .returnRow("prefix", "given", "middle", "family", "suffix")
    887                 .withProjection(StructuredName.PREFIX, StructuredName.GIVEN_NAME,
    888                         StructuredName.MIDDLE_NAME, StructuredName.FAMILY_NAME,
    889                         StructuredName.SUFFIX);
    890 
    891         RawContactDelta oldState = new RawContactDelta();
    892         ContentValues mockNameValues = new ContentValues();
    893         mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    894         mockNameValues.put(StructuredName.DISPLAY_NAME, inputDisplayName);
    895         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
    896 
    897         RawContactDelta newState = new RawContactDelta();
    898         RawContactModifier.migrateStructuredName(context, oldState, newState, kind);
    899         List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
    900         assertEquals(1, list.size());
    901 
    902         ContentValues outputValues = list.get(0).getAfter();
    903         assertEquals("prefix", outputValues.getAsString(StructuredName.PREFIX));
    904         assertEquals("given", outputValues.getAsString(StructuredName.GIVEN_NAME));
    905         assertEquals("middle", outputValues.getAsString(StructuredName.MIDDLE_NAME));
    906         assertEquals("family", outputValues.getAsString(StructuredName.FAMILY_NAME));
    907         assertEquals("suffix", outputValues.getAsString(StructuredName.SUFFIX));
    908     }
    909 
    910     public void testMigrateWithStructuredNameFromExchangeToGoogle() {
    911         AccountType oldAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
    912         AccountType newAccountType = new GoogleAccountType(getContext(), "");
    913         DataKind kind = newAccountType.getKindForMimetype(StructuredName.CONTENT_ITEM_TYPE);
    914 
    915         ContactsMockContext context = new ContactsMockContext(getContext());
    916         MockContentProvider provider = context.getContactsProvider();
    917 
    918         // The method will ask the provider to split/join StructuredName.
    919         Uri uriForBuildDisplayName =
    920                 ContactsContract.AUTHORITY_URI
    921                         .buildUpon()
    922                         .appendPath("complete_name")
    923                         .appendQueryParameter(StructuredName.PREFIX, "prefix")
    924                         .appendQueryParameter(StructuredName.GIVEN_NAME, "given")
    925                         .appendQueryParameter(StructuredName.MIDDLE_NAME, "middle")
    926                         .appendQueryParameter(StructuredName.FAMILY_NAME, "family")
    927                         .appendQueryParameter(StructuredName.SUFFIX, "suffix")
    928                         .build();
    929         provider.expectQuery(uriForBuildDisplayName)
    930                 .returnRow("prefix given middle family suffix")
    931                 .withProjection(StructuredName.DISPLAY_NAME);
    932 
    933         RawContactDelta oldState = new RawContactDelta();
    934         ContentValues mockNameValues = new ContentValues();
    935         mockNameValues.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    936         mockNameValues.put(StructuredName.PREFIX, "prefix");
    937         mockNameValues.put(StructuredName.GIVEN_NAME, "given");
    938         mockNameValues.put(StructuredName.MIDDLE_NAME, "middle");
    939         mockNameValues.put(StructuredName.FAMILY_NAME, "family");
    940         mockNameValues.put(StructuredName.SUFFIX, "suffix");
    941         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
    942 
    943         RawContactDelta newState = new RawContactDelta();
    944         RawContactModifier.migrateStructuredName(context, oldState, newState, kind);
    945 
    946         List<ValuesDelta> list = newState.getMimeEntries(StructuredName.CONTENT_ITEM_TYPE);
    947         assertNotNull(list);
    948         assertEquals(1, list.size());
    949         ContentValues outputValues = list.get(0).getAfter();
    950         assertEquals("prefix given middle family suffix",
    951                 outputValues.getAsString(StructuredName.DISPLAY_NAME));
    952     }
    953 
    954     public void testMigratePostalFromGoogleToExchange() {
    955         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
    956         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
    957         DataKind kind = newAccountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
    958 
    959         RawContactDelta oldState = new RawContactDelta();
    960         ContentValues mockNameValues = new ContentValues();
    961         mockNameValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
    962         mockNameValues.put(StructuredPostal.FORMATTED_ADDRESS, "formatted_address");
    963         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
    964 
    965         RawContactDelta newState = new RawContactDelta();
    966         RawContactModifier.migratePostal(oldState, newState, kind);
    967 
    968         List<ValuesDelta> list = newState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE);
    969         assertNotNull(list);
    970         assertEquals(1, list.size());
    971         ContentValues outputValues = list.get(0).getAfter();
    972         // FORMATTED_ADDRESS isn't supported by Exchange.
    973         assertNull(outputValues.getAsString(StructuredPostal.FORMATTED_ADDRESS));
    974         assertEquals("formatted_address", outputValues.getAsString(StructuredPostal.STREET));
    975     }
    976 
    977     public void testMigratePostalFromExchangeToGoogle() {
    978         AccountType oldAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
    979         AccountType newAccountType = new GoogleAccountType(getContext(), "");
    980         DataKind kind = newAccountType.getKindForMimetype(StructuredPostal.CONTENT_ITEM_TYPE);
    981 
    982         RawContactDelta oldState = new RawContactDelta();
    983         ContentValues mockNameValues = new ContentValues();
    984         mockNameValues.put(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE);
    985         mockNameValues.put(StructuredPostal.COUNTRY, "country");
    986         mockNameValues.put(StructuredPostal.POSTCODE, "postcode");
    987         mockNameValues.put(StructuredPostal.REGION, "region");
    988         mockNameValues.put(StructuredPostal.CITY, "city");
    989         mockNameValues.put(StructuredPostal.STREET, "street");
    990         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
    991 
    992         RawContactDelta newState = new RawContactDelta();
    993         RawContactModifier.migratePostal(oldState, newState, kind);
    994 
    995         List<ValuesDelta> list = newState.getMimeEntries(StructuredPostal.CONTENT_ITEM_TYPE);
    996         assertNotNull(list);
    997         assertEquals(1, list.size());
    998         ContentValues outputValues = list.get(0).getAfter();
    999 
   1000         // Check FORMATTED_ADDRESS contains all info.
   1001         String formattedAddress = outputValues.getAsString(StructuredPostal.FORMATTED_ADDRESS);
   1002         assertNotNull(formattedAddress);
   1003         assertTrue(formattedAddress.contains("country"));
   1004         assertTrue(formattedAddress.contains("postcode"));
   1005         assertTrue(formattedAddress.contains("region"));
   1006         assertTrue(formattedAddress.contains("postcode"));
   1007         assertTrue(formattedAddress.contains("city"));
   1008         assertTrue(formattedAddress.contains("street"));
   1009     }
   1010 
   1011     public void testMigrateEventFromGoogleToExchange1() {
   1012         testMigrateEventCommon(new GoogleAccountType(getContext(), ""),
   1013                 new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE));
   1014     }
   1015 
   1016     public void testMigrateEventFromExchangeToGoogle() {
   1017         testMigrateEventCommon(new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE),
   1018                 new GoogleAccountType(getContext(), ""));
   1019     }
   1020 
   1021     private void testMigrateEventCommon(AccountType oldAccountType, AccountType newAccountType) {
   1022         DataKind kind = newAccountType.getKindForMimetype(Event.CONTENT_ITEM_TYPE);
   1023 
   1024         RawContactDelta oldState = new RawContactDelta();
   1025         ContentValues mockNameValues = new ContentValues();
   1026         mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
   1027         mockNameValues.put(Event.START_DATE, "1972-02-08");
   1028         mockNameValues.put(Event.TYPE, Event.TYPE_BIRTHDAY);
   1029         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1030 
   1031         RawContactDelta newState = new RawContactDelta();
   1032         RawContactModifier.migrateEvent(oldState, newState, kind, 1990);
   1033 
   1034         List<ValuesDelta> list = newState.getMimeEntries(Event.CONTENT_ITEM_TYPE);
   1035         assertNotNull(list);
   1036         assertEquals(1, list.size());  // Anniversary should be dropped.
   1037         ContentValues outputValues = list.get(0).getAfter();
   1038 
   1039         assertEquals("1972-02-08", outputValues.getAsString(Event.START_DATE));
   1040         assertEquals(Event.TYPE_BIRTHDAY, outputValues.getAsInteger(Event.TYPE).intValue());
   1041     }
   1042 
   1043     public void testMigrateEventFromGoogleToExchange2() {
   1044         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
   1045         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
   1046         DataKind kind = newAccountType.getKindForMimetype(Event.CONTENT_ITEM_TYPE);
   1047 
   1048         RawContactDelta oldState = new RawContactDelta();
   1049         ContentValues mockNameValues = new ContentValues();
   1050         mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
   1051         // No year format is not supported by Exchange.
   1052         mockNameValues.put(Event.START_DATE, "--06-01");
   1053         mockNameValues.put(Event.TYPE, Event.TYPE_BIRTHDAY);
   1054         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1055         mockNameValues = new ContentValues();
   1056         mockNameValues.put(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE);
   1057         mockNameValues.put(Event.START_DATE, "1980-08-02");
   1058         // Anniversary is not supported by Exchange
   1059         mockNameValues.put(Event.TYPE, Event.TYPE_ANNIVERSARY);
   1060         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1061 
   1062         RawContactDelta newState = new RawContactDelta();
   1063         RawContactModifier.migrateEvent(oldState, newState, kind, 1990);
   1064 
   1065         List<ValuesDelta> list = newState.getMimeEntries(Event.CONTENT_ITEM_TYPE);
   1066         assertNotNull(list);
   1067         assertEquals(1, list.size());  // Anniversary should be dropped.
   1068         ContentValues outputValues = list.get(0).getAfter();
   1069 
   1070         // Default year should be used.
   1071         assertEquals("1990-06-01", outputValues.getAsString(Event.START_DATE));
   1072         assertEquals(Event.TYPE_BIRTHDAY, outputValues.getAsInteger(Event.TYPE).intValue());
   1073     }
   1074 
   1075     public void testMigrateEmailFromGoogleToExchange() {
   1076         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
   1077         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
   1078         DataKind kind = newAccountType.getKindForMimetype(Email.CONTENT_ITEM_TYPE);
   1079 
   1080         RawContactDelta oldState = new RawContactDelta();
   1081         ContentValues mockNameValues = new ContentValues();
   1082         mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
   1083         mockNameValues.put(Email.TYPE, Email.TYPE_CUSTOM);
   1084         mockNameValues.put(Email.LABEL, "custom_type");
   1085         mockNameValues.put(Email.ADDRESS, "address1");
   1086         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1087         mockNameValues = new ContentValues();
   1088         mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
   1089         mockNameValues.put(Email.TYPE, Email.TYPE_HOME);
   1090         mockNameValues.put(Email.ADDRESS, "address2");
   1091         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1092         mockNameValues = new ContentValues();
   1093         mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
   1094         mockNameValues.put(Email.TYPE, Email.TYPE_WORK);
   1095         mockNameValues.put(Email.ADDRESS, "address3");
   1096         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1097         // Exchange can have up to 3 email entries. This 4th entry should be dropped.
   1098         mockNameValues = new ContentValues();
   1099         mockNameValues.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
   1100         mockNameValues.put(Email.TYPE, Email.TYPE_OTHER);
   1101         mockNameValues.put(Email.ADDRESS, "address4");
   1102         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1103 
   1104         RawContactDelta newState = new RawContactDelta();
   1105         RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
   1106 
   1107         List<ValuesDelta> list = newState.getMimeEntries(Email.CONTENT_ITEM_TYPE);
   1108         assertNotNull(list);
   1109         assertEquals(3, list.size());
   1110 
   1111         ContentValues outputValues = list.get(0).getAfter();
   1112         assertEquals(Email.TYPE_CUSTOM, outputValues.getAsInteger(Email.TYPE).intValue());
   1113         assertEquals("custom_type", outputValues.getAsString(Email.LABEL));
   1114         assertEquals("address1", outputValues.getAsString(Email.ADDRESS));
   1115 
   1116         outputValues = list.get(1).getAfter();
   1117         assertEquals(Email.TYPE_HOME, outputValues.getAsInteger(Email.TYPE).intValue());
   1118         assertEquals("address2", outputValues.getAsString(Email.ADDRESS));
   1119 
   1120         outputValues = list.get(2).getAfter();
   1121         assertEquals(Email.TYPE_WORK, outputValues.getAsInteger(Email.TYPE).intValue());
   1122         assertEquals("address3", outputValues.getAsString(Email.ADDRESS));
   1123     }
   1124 
   1125     public void testMigrateImFromGoogleToExchange() {
   1126         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
   1127         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
   1128         DataKind kind = newAccountType.getKindForMimetype(Im.CONTENT_ITEM_TYPE);
   1129 
   1130         RawContactDelta oldState = new RawContactDelta();
   1131         ContentValues mockNameValues = new ContentValues();
   1132         mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
   1133         // Exchange doesn't support TYPE_HOME
   1134         mockNameValues.put(Im.TYPE, Im.TYPE_HOME);
   1135         mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_JABBER);
   1136         mockNameValues.put(Im.DATA, "im1");
   1137         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1138 
   1139         mockNameValues = new ContentValues();
   1140         mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
   1141         // Exchange doesn't support TYPE_WORK
   1142         mockNameValues.put(Im.TYPE, Im.TYPE_WORK);
   1143         mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_YAHOO);
   1144         mockNameValues.put(Im.DATA, "im2");
   1145         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1146 
   1147         mockNameValues = new ContentValues();
   1148         mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
   1149         mockNameValues.put(Im.TYPE, Im.TYPE_OTHER);
   1150         mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_CUSTOM);
   1151         mockNameValues.put(Im.CUSTOM_PROTOCOL, "custom_protocol");
   1152         mockNameValues.put(Im.DATA, "im3");
   1153         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1154 
   1155         // Exchange can have up to 3 IM entries. This 4th entry should be dropped.
   1156         mockNameValues = new ContentValues();
   1157         mockNameValues.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
   1158         mockNameValues.put(Im.TYPE, Im.TYPE_OTHER);
   1159         mockNameValues.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
   1160         mockNameValues.put(Im.DATA, "im4");
   1161         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1162 
   1163         RawContactDelta newState = new RawContactDelta();
   1164         RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
   1165 
   1166         List<ValuesDelta> list = newState.getMimeEntries(Im.CONTENT_ITEM_TYPE);
   1167         assertNotNull(list);
   1168         assertEquals(3, list.size());
   1169 
   1170         assertNotNull(kind.defaultValues.getAsInteger(Im.TYPE));
   1171 
   1172         int defaultType = kind.defaultValues.getAsInteger(Im.TYPE);
   1173 
   1174         ContentValues outputValues = list.get(0).getAfter();
   1175         // HOME should become default type.
   1176         assertEquals(defaultType, outputValues.getAsInteger(Im.TYPE).intValue());
   1177         assertEquals(Im.PROTOCOL_JABBER, outputValues.getAsInteger(Im.PROTOCOL).intValue());
   1178         assertEquals("im1", outputValues.getAsString(Im.DATA));
   1179 
   1180         outputValues = list.get(1).getAfter();
   1181         assertEquals(defaultType, outputValues.getAsInteger(Im.TYPE).intValue());
   1182         assertEquals(Im.PROTOCOL_YAHOO, outputValues.getAsInteger(Im.PROTOCOL).intValue());
   1183         assertEquals("im2", outputValues.getAsString(Im.DATA));
   1184 
   1185         outputValues = list.get(2).getAfter();
   1186         assertEquals(defaultType, outputValues.getAsInteger(Im.TYPE).intValue());
   1187         assertEquals(Im.PROTOCOL_CUSTOM, outputValues.getAsInteger(Im.PROTOCOL).intValue());
   1188         assertEquals("custom_protocol", outputValues.getAsString(Im.CUSTOM_PROTOCOL));
   1189         assertEquals("im3", outputValues.getAsString(Im.DATA));
   1190     }
   1191 
   1192     public void testMigratePhoneFromGoogleToExchange() {
   1193         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
   1194         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
   1195         DataKind kind = newAccountType.getKindForMimetype(Phone.CONTENT_ITEM_TYPE);
   1196 
   1197         // Create 5 numbers.
   1198         // - "1" -- HOME
   1199         // - "2" -- WORK
   1200         // - "3" -- CUSTOM
   1201         // - "4" -- WORK
   1202         // - "5" -- WORK_MOBILE
   1203         // Then we convert it to Exchange account type.
   1204         // - "1" -- HOME
   1205         // - "2" -- WORK
   1206         // - "3" -- Because CUSTOM is not supported, it'll be changed to the default, MOBILE
   1207         // - "4" -- WORK
   1208         // - "5" -- WORK_MOBILE not suppoted again, so will be MOBILE.
   1209         // But then, Exchange doesn't support multiple MOBILE numbers, so "5" will be removed.
   1210         // i.e. the result will be:
   1211         // - "1" -- HOME
   1212         // - "2" -- WORK
   1213         // - "3" -- MOBILE
   1214         // - "4" -- WORK
   1215 
   1216         RawContactDelta oldState = new RawContactDelta();
   1217         ContentValues mockNameValues = new ContentValues();
   1218         mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1219         mockNameValues.put(Phone.TYPE, Phone.TYPE_HOME);
   1220         mockNameValues.put(Phone.NUMBER, "1");
   1221         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1222         mockNameValues = new ContentValues();
   1223         mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1224         mockNameValues.put(Phone.TYPE, Phone.TYPE_WORK);
   1225         mockNameValues.put(Phone.NUMBER, "2");
   1226         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1227         mockNameValues = new ContentValues();
   1228         mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1229         // Exchange doesn't support this type. Default to MOBILE
   1230         mockNameValues.put(Phone.TYPE, Phone.TYPE_CUSTOM);
   1231         mockNameValues.put(Phone.LABEL, "custom_type");
   1232         mockNameValues.put(Phone.NUMBER, "3");
   1233         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1234         mockNameValues = new ContentValues();
   1235         mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1236         mockNameValues.put(Phone.TYPE, Phone.TYPE_WORK);
   1237         mockNameValues.put(Phone.NUMBER, "4");
   1238         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1239         mockNameValues = new ContentValues();
   1240 
   1241         mockNameValues.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
   1242         mockNameValues.put(Phone.TYPE, Phone.TYPE_WORK_MOBILE);
   1243         mockNameValues.put(Phone.NUMBER, "5");
   1244         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1245 
   1246         RawContactDelta newState = new RawContactDelta();
   1247         RawContactModifier.migrateGenericWithTypeColumn(oldState, newState, kind);
   1248 
   1249         List<ValuesDelta> list = newState.getMimeEntries(Phone.CONTENT_ITEM_TYPE);
   1250         assertNotNull(list);
   1251         assertEquals(4, list.size());
   1252 
   1253         int defaultType = Phone.TYPE_MOBILE;
   1254 
   1255         ContentValues outputValues = list.get(0).getAfter();
   1256         assertEquals(Phone.TYPE_HOME, outputValues.getAsInteger(Phone.TYPE).intValue());
   1257         assertEquals("1", outputValues.getAsString(Phone.NUMBER));
   1258         outputValues = list.get(1).getAfter();
   1259         assertEquals(Phone.TYPE_WORK, outputValues.getAsInteger(Phone.TYPE).intValue());
   1260         assertEquals("2", outputValues.getAsString(Phone.NUMBER));
   1261         outputValues = list.get(2).getAfter();
   1262         assertEquals(defaultType, outputValues.getAsInteger(Phone.TYPE).intValue());
   1263         assertNull(outputValues.getAsInteger(Phone.LABEL));
   1264         assertEquals("3", outputValues.getAsString(Phone.NUMBER));
   1265         outputValues = list.get(3).getAfter();
   1266         assertEquals(Phone.TYPE_WORK, outputValues.getAsInteger(Phone.TYPE).intValue());
   1267         assertEquals("4", outputValues.getAsString(Phone.NUMBER));
   1268     }
   1269 
   1270     public void testMigrateOrganizationFromGoogleToExchange() {
   1271         AccountType oldAccountType = new GoogleAccountType(getContext(), "");
   1272         AccountType newAccountType = new ExchangeAccountType(getContext(), "", EXCHANGE_ACCT_TYPE);
   1273         DataKind kind = newAccountType.getKindForMimetype(Organization.CONTENT_ITEM_TYPE);
   1274 
   1275         RawContactDelta oldState = new RawContactDelta();
   1276         ContentValues mockNameValues = new ContentValues();
   1277         mockNameValues.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
   1278         mockNameValues.put(Organization.COMPANY, "company1");
   1279         mockNameValues.put(Organization.DEPARTMENT, "department1");
   1280         oldState.addEntry(ValuesDelta.fromAfter(mockNameValues));
   1281 
   1282         RawContactDelta newState = new RawContactDelta();
   1283         RawContactModifier.migrateGenericWithoutTypeColumn(oldState, newState, kind);
   1284 
   1285         List<ValuesDelta> list = newState.getMimeEntries(Organization.CONTENT_ITEM_TYPE);
   1286         assertNotNull(list);
   1287         assertEquals(1, list.size());
   1288 
   1289         ContentValues outputValues = list.get(0).getAfter();
   1290         assertEquals("company1", outputValues.getAsString(Organization.COMPANY));
   1291         assertEquals("department1", outputValues.getAsString(Organization.DEPARTMENT));
   1292     }
   1293 }
   1294