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
17package com.android.contacts.common;
18
19import android.content.ContentProviderOperation;
20import android.content.ContentValues;
21import android.net.Uri;
22import android.os.Bundle;
23import android.provider.ContactsContract;
24import android.provider.ContactsContract.CommonDataKinds.Email;
25import android.provider.ContactsContract.CommonDataKinds.Event;
26import android.provider.ContactsContract.CommonDataKinds.Im;
27import android.provider.ContactsContract.CommonDataKinds.Organization;
28import android.provider.ContactsContract.CommonDataKinds.Phone;
29import android.provider.ContactsContract.CommonDataKinds.StructuredName;
30import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
31import android.provider.ContactsContract.Data;
32import android.provider.ContactsContract.Intents.Insert;
33import android.provider.ContactsContract.RawContacts;
34import android.test.AndroidTestCase;
35import android.test.suitebuilder.annotation.LargeTest;
36
37import com.android.contacts.common.compat.CompatUtils;
38import com.android.contacts.common.model.AccountTypeManager;
39import com.android.contacts.common.model.CPOWrapper;
40import com.android.contacts.common.model.RawContact;
41import com.android.contacts.common.model.RawContactDelta;
42import com.android.contacts.common.model.ValuesDelta;
43import com.android.contacts.common.model.RawContactDeltaList;
44import com.android.contacts.common.model.RawContactModifier;
45import com.android.contacts.common.model.account.AccountType;
46import com.android.contacts.common.model.account.AccountType.EditType;
47import com.android.contacts.common.model.account.ExchangeAccountType;
48import com.android.contacts.common.model.account.GoogleAccountType;
49import com.android.contacts.common.model.dataitem.DataKind;
50import com.android.contacts.common.test.mocks.ContactsMockContext;
51import com.android.contacts.common.test.mocks.MockAccountTypeManager;
52import com.android.contacts.common.test.mocks.MockContentProvider;
53import com.google.common.collect.Lists;
54
55import java.util.ArrayList;
56import java.util.List;
57
58/**
59 * Tests for {@link RawContactModifier} to verify that {@link AccountType}
60 * constraints are being enforced correctly.
61 */
62@LargeTest
63public 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@example.com";
76    private static final String TEST_POSTAL = "1600 Amphitheatre Parkway";
77
78    private static final String TEST_ACCOUNT_NAME = "unittest@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