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