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