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