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