ContactAggregatorTest.java revision f256a7cf7f78d5ff2573358eb2eadd6f3a2ec9db
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.providers.contacts.aggregation;
18
19import android.accounts.Account;
20import android.content.ContentProviderOperation;
21import android.content.ContentProviderResult;
22import android.content.ContentUris;
23import android.content.ContentValues;
24import android.database.Cursor;
25import android.net.Uri;
26import android.provider.ContactsContract;
27import android.provider.ContactsContract.AggregationExceptions;
28import android.provider.ContactsContract.CommonDataKinds.Organization;
29import android.provider.ContactsContract.CommonDataKinds.StructuredName;
30import android.provider.ContactsContract.Contacts;
31import android.provider.ContactsContract.Contacts.AggregationSuggestions;
32import android.provider.ContactsContract.Contacts.Photo;
33import android.provider.ContactsContract.Data;
34import android.provider.ContactsContract.RawContacts;
35import android.provider.ContactsContract.StatusUpdates;
36import android.test.MoreAsserts;
37import android.test.suitebuilder.annotation.MediumTest;
38
39import com.android.providers.contacts.BaseContactsProvider2Test;
40import com.android.providers.contacts.TestUtils;
41import com.android.providers.contacts.tests.R;
42import com.android.providers.contacts.testutil.DataUtil;
43import com.android.providers.contacts.testutil.RawContactUtil;
44
45import com.google.android.collect.Lists;
46import com.google.common.collect.HashMultimap;
47import com.google.common.collect.Multimap;
48
49import java.util.Arrays;
50import java.util.HashSet;
51import java.util.Set;
52
53/**
54 * Unit tests for {@link ContactAggregator}.
55 *
56 * Run the test like this:
57 * <code>
58 * adb shell am instrument -e \
59 *         class com.android.providers.contacts.aggregation.ContactAggregatorTest -w \
60 *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
61 * </code>
62 */
63@MediumTest
64public class ContactAggregatorTest extends BaseContactsProvider2Test {
65
66    private static final Account ACCOUNT_1 = new Account("account_name_1", "account_type_1");
67    private static final Account ACCOUNT_2 = new Account("account_name_2", "account_type_2");
68    private static final Account ACCOUNT_3 = new Account("account_name_3", "account_type_3");
69
70    private static final String[] AGGREGATION_EXCEPTION_PROJECTION = new String[] {
71            AggregationExceptions.TYPE,
72            AggregationExceptions.RAW_CONTACT_ID1,
73            AggregationExceptions.RAW_CONTACT_ID2
74    };
75
76    public void testCrudAggregationExceptions() throws Exception {
77        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "zz", "top");
78        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "aa", "bottom");
79
80        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
81                rawContactId1, rawContactId2);
82
83        String selection = "(" + AggregationExceptions.RAW_CONTACT_ID1 + "=" + rawContactId1
84                + " AND " + AggregationExceptions.RAW_CONTACT_ID2 + "=" + rawContactId2
85                + ") OR (" + AggregationExceptions.RAW_CONTACT_ID1 + "=" + rawContactId2
86                + " AND " + AggregationExceptions.RAW_CONTACT_ID2 + "=" + rawContactId1 + ")";
87
88        // Refetch the row we have just inserted
89        Cursor c = mResolver.query(AggregationExceptions.CONTENT_URI,
90                AGGREGATION_EXCEPTION_PROJECTION, selection, null, null);
91
92        assertTrue(c.moveToFirst());
93        assertEquals(AggregationExceptions.TYPE_KEEP_TOGETHER, c.getInt(0));
94        assertTrue((rawContactId1 == c.getLong(1) && rawContactId2 == c.getLong(2))
95                || (rawContactId2 == c.getLong(1) && rawContactId1 == c.getLong(2)));
96        assertFalse(c.moveToNext());
97        c.close();
98
99        // Change from TYPE_KEEP_IN to TYPE_KEEP_OUT
100        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
101                rawContactId1, rawContactId2);
102
103        c = mResolver.query(AggregationExceptions.CONTENT_URI, AGGREGATION_EXCEPTION_PROJECTION,
104                selection, null, null);
105
106        assertTrue(c.moveToFirst());
107        assertEquals(AggregationExceptions.TYPE_KEEP_SEPARATE, c.getInt(0));
108        assertTrue((rawContactId1 == c.getLong(1) && rawContactId2 == c.getLong(2))
109                || (rawContactId2 == c.getLong(1) && rawContactId1 == c.getLong(2)));
110        assertFalse(c.moveToNext());
111        c.close();
112
113        // Delete the rule
114        setAggregationException(AggregationExceptions.TYPE_AUTOMATIC,
115                rawContactId1, rawContactId2);
116
117        // Verify that the row is gone
118        c = mResolver.query(AggregationExceptions.CONTENT_URI, AGGREGATION_EXCEPTION_PROJECTION,
119                selection, null, null);
120        assertFalse(c.moveToFirst());
121        c.close();
122    }
123
124    public void testAggregationCreatesNewAggregate() {
125        long rawContactId = RawContactUtil.createRawContact(mResolver);
126
127        Uri resultUri = DataUtil.insertStructuredName(mResolver, rawContactId, "Johna", "Smitha");
128
129        // Parse the URI and confirm that it contains an ID
130        assertTrue(ContentUris.parseId(resultUri) != 0);
131
132        long contactId = queryContactId(rawContactId);
133        assertTrue(contactId != 0);
134
135        String displayName = queryDisplayName(contactId);
136        assertEquals("Johna Smitha", displayName);
137    }
138
139    public void testAggregationOfExactFullNameMatch() {
140        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
141        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnb", "Smithb");
142
143        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
144        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnb", "Smithb");
145
146        assertAggregated(rawContactId1, rawContactId2, "Johnb Smithb");
147    }
148
149    public void testAggregationIgnoresInvisibleContact() {
150        Account account = new Account("accountName", "accountType");
151        createAutoAddGroup(account);
152
153        long rawContactId1 = RawContactUtil.createRawContact(mResolver, account);
154        DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder");
155
156        // Hide by removing from all groups
157        removeGroupMemberships(rawContactId1);
158
159        long rawContactId2 = RawContactUtil.createRawContact(mResolver, account);
160        DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder");
161
162        long rawContactId3 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
163        DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder");
164
165        assertNotAggregated(rawContactId1, rawContactId2);
166        assertNotAggregated(rawContactId1, rawContactId3);
167        assertAggregated(rawContactId2, rawContactId3, "Flynn Ryder");
168    }
169
170    public void testAggregationOfCaseInsensitiveFullNameMatch() {
171        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
172        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnc", "Smithc");
173
174        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
175        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnc", "smithc");
176
177        assertAggregated(rawContactId1, rawContactId2, "Johnc Smithc");
178    }
179
180    public void testAggregationOfLastNameMatch() {
181        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
182        DataUtil.insertStructuredName(mResolver, rawContactId1, null, "Johnd");
183
184        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
185        DataUtil.insertStructuredName(mResolver, rawContactId2, null, "johnd");
186
187        assertAggregated(rawContactId1, rawContactId2, "Johnd");
188    }
189
190    public void testNonAggregationOfFirstNameMatch() {
191        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
192        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johne", "Smithe");
193
194        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
195        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johne", null);
196
197        assertNotAggregated(rawContactId1, rawContactId2);
198    }
199
200    public void testNonAggregationOfLastNameMatch() {
201        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
202        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnf", "Smithf");
203
204        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
205        DataUtil.insertStructuredName(mResolver, rawContactId2, null, "Smithf");
206
207        assertNotAggregated(rawContactId1, rawContactId2);
208    }
209
210    public void testAggregationOfConcatenatedFullNameMatch() {
211        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
212        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johng", "Smithg");
213
214        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
215        DataUtil.insertStructuredName(mResolver, rawContactId2, "johngsmithg", null);
216
217        assertAggregated(rawContactId1, rawContactId2, "Johng Smithg");
218    }
219
220    public void testAggregationOfNormalizedFullNameMatch() {
221        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
222        DataUtil.insertStructuredName(mResolver, rawContactId1, "H\u00e9l\u00e8ne", "Bj\u00f8rn");
223
224        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
225        DataUtil.insertStructuredName(mResolver, rawContactId2, "helene bjorn", null);
226
227        assertAggregated(rawContactId1, rawContactId2, "H\u00e9l\u00e8ne Bj\u00f8rn");
228    }
229
230    public void testAggregationOfNormalizedFullNameMatchWithReadOnlyAccount() {
231        long rawContactId1 = RawContactUtil.createRawContact(mResolver, new Account("acct",
232                READ_ONLY_ACCOUNT_TYPE));
233        DataUtil.insertStructuredName(mResolver, rawContactId1, "H\u00e9l\u00e8ne", "Bj\u00f8rn");
234
235        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
236        DataUtil.insertStructuredName(mResolver, rawContactId2, "helene bjorn", null);
237
238        assertAggregated(rawContactId1, rawContactId2, "helene bjorn");
239    }
240
241    public void testAggregationOfNumericNames() {
242        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
243        DataUtil.insertStructuredName(mResolver, rawContactId1, "123", null);
244
245        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
246        DataUtil.insertStructuredName(mResolver, rawContactId2, "1-2-3", null);
247
248        assertAggregated(rawContactId1, rawContactId2, "1-2-3");
249    }
250
251    public void testAggregationOfInconsistentlyParsedNames() {
252        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
253
254        ContentValues values = new ContentValues();
255        values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave");
256        values.put(StructuredName.GIVEN_NAME, "604");
257        values.put(StructuredName.MIDDLE_NAME, "Arizona");
258        values.put(StructuredName.FAMILY_NAME, "Ave");
259        DataUtil.insertStructuredName(mResolver, rawContactId1, values);
260
261        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
262        values.clear();
263        values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave");
264        values.put(StructuredName.GIVEN_NAME, "604");
265        values.put(StructuredName.FAMILY_NAME, "Arizona Ave");
266        DataUtil.insertStructuredName(mResolver, rawContactId2, values);
267
268        assertAggregated(rawContactId1, rawContactId2, "604 Arizona Ave");
269    }
270
271    public void testAggregationBasedOnMiddleName() {
272        ContentValues values = new ContentValues();
273        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
274        values.put(StructuredName.GIVEN_NAME, "John");
275        values.put(StructuredName.GIVEN_NAME, "Abigale");
276        values.put(StructuredName.FAMILY_NAME, "James");
277
278        DataUtil.insertStructuredName(mResolver, rawContactId1, values);
279
280        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
281        values.clear();
282        values.put(StructuredName.GIVEN_NAME, "John");
283        values.put(StructuredName.GIVEN_NAME, "Marie");
284        values.put(StructuredName.FAMILY_NAME, "James");
285        DataUtil.insertStructuredName(mResolver, rawContactId2, values);
286
287        assertNotAggregated(rawContactId1, rawContactId2);
288    }
289
290    public void testAggregationBasedOnPhoneNumberNoNameData() {
291        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
292        insertPhoneNumber(rawContactId1, "(888)555-1231");
293
294        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
295        insertPhoneNumber(rawContactId2, "1(888)555-1231");
296
297        assertAggregated(rawContactId1, rawContactId2);
298    }
299
300    public void testAggregationBasedOnPhoneNumberWhenTargetAggregateHasNoName() {
301        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
302        insertPhoneNumber(rawContactId1, "(888)555-1232");
303
304        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
305        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnl", "Smithl");
306        insertPhoneNumber(rawContactId2, "1(888)555-1232");
307
308        assertAggregated(rawContactId1, rawContactId2);
309    }
310
311    public void testAggregationBasedOnPhoneNumberWhenNewContactHasNoName() {
312        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
313        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnm", "Smithm");
314        insertPhoneNumber(rawContactId1, "(888)555-1233");
315
316        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
317        insertPhoneNumber(rawContactId2, "1(888)555-1233");
318
319        assertAggregated(rawContactId1, rawContactId2);
320    }
321
322    public void testAggregationBasedOnPhoneNumberWithDifferentNames() {
323        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
324        DataUtil.insertStructuredName(mResolver, rawContactId1, "Baby", "Bear");
325        insertPhoneNumber(rawContactId1, "(888)555-1235");
326
327        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
328        DataUtil.insertStructuredName(mResolver, rawContactId2, "Blind", "Mouse");
329        insertPhoneNumber(rawContactId2, "1(888)555-1235");
330
331        assertNotAggregated(rawContactId1, rawContactId2);
332    }
333
334    public void testAggregationBasedOnPhoneNumberWithJustFirstName() {
335        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
336        DataUtil.insertStructuredName(mResolver, rawContactId1, "Chick", "Notnull");
337        insertPhoneNumber(rawContactId1, "(888)555-1236");
338
339        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
340        DataUtil.insertStructuredName(mResolver, rawContactId2, "Chick", null);
341        insertPhoneNumber(rawContactId2, "1(888)555-1236");
342
343        assertAggregated(rawContactId1, rawContactId2);
344    }
345
346    public void testAggregationBasedOnEmailNoNameData() {
347        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
348        insertEmail(rawContactId1, "lightning@android.com");
349
350        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
351        insertEmail(rawContactId2, "lightning@android.com");
352
353        assertAggregated(rawContactId1, rawContactId2);
354    }
355
356    public void testAggregationBasedOnEmailWhenTargetAggregateHasNoName() {
357        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
358        insertEmail(rawContactId1, "mcqueen@android.com");
359
360        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
361        DataUtil.insertStructuredName(mResolver, rawContactId2, "Lightning", "McQueen");
362        insertEmail(rawContactId2, "mcqueen@android.com");
363
364        assertAggregated(rawContactId1, rawContactId2, "Lightning McQueen");
365    }
366
367    public void testAggregationBasedOnEmailWhenNewContactHasNoName() {
368        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
369        DataUtil.insertStructuredName(mResolver, rawContactId1, "Doc", "Hudson");
370        insertEmail(rawContactId1, "doc@android.com");
371
372        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
373        insertEmail(rawContactId2, "doc@android.com");
374
375        assertAggregated(rawContactId1, rawContactId2);
376    }
377
378    public void testAggregationBasedOnEmailWithDifferentNames() {
379        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
380        DataUtil.insertStructuredName(mResolver, rawContactId1, "Chick", "Hicks");
381        insertEmail(rawContactId1, "hicky@android.com");
382
383        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
384        DataUtil.insertStructuredName(mResolver, rawContactId2, "Luigi", "Guido");
385        insertEmail(rawContactId2, "hicky@android.com");
386
387        assertNotAggregated(rawContactId1, rawContactId2);
388    }
389
390    public void testAggregationByCommonNicknameWithLastName() {
391        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
392        DataUtil.insertStructuredName(mResolver, rawContactId1, "Bill", "Gore");
393
394        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
395        DataUtil.insertStructuredName(mResolver, rawContactId2, "William", "Gore");
396
397        assertAggregated(rawContactId1, rawContactId2, "William Gore");
398    }
399
400    public void testAggregationByCommonNicknameOnly() {
401        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
402        DataUtil.insertStructuredName(mResolver, rawContactId1, "Lawrence", null);
403
404        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
405        DataUtil.insertStructuredName(mResolver, rawContactId2, "Larry", null);
406
407        assertAggregated(rawContactId1, rawContactId2, "Lawrence");
408    }
409
410    public void testAggregationByNicknameNoStructuredName() {
411        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
412        insertNickname(rawContactId1, "Frozone");
413
414        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
415        insertNickname(rawContactId2, "Frozone");
416
417        assertAggregated(rawContactId1, rawContactId2);
418    }
419
420    public void testAggregationByNicknameWithDifferentNames() {
421        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
422        DataUtil.insertStructuredName(mResolver, rawContactId1, "Helen", "Parr");
423        insertNickname(rawContactId1, "Elastigirl");
424
425        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
426        DataUtil.insertStructuredName(mResolver, rawContactId2, "Shawn", "Johnson");
427        insertNickname(rawContactId2, "Elastigirl");
428
429        assertNotAggregated(rawContactId1, rawContactId2);
430    }
431
432    public void testNonAggregationOnOrganization() {
433        ContentValues values = new ContentValues();
434        values.put(Organization.TITLE, "Monsters, Inc");
435        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
436        insertOrganization(rawContactId1, values);
437        insertNickname(rawContactId1, "Boo");
438
439        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
440        insertOrganization(rawContactId2, values);
441        insertNickname(rawContactId2, "Rendall");   // To force reaggregation
442
443        assertNotAggregated(rawContactId1, rawContactId2);
444    }
445
446    public void testAggregationByIdentity() {
447        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
448        insertIdentity(rawContactId1, "iden1", "namespace1");
449
450        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
451        insertIdentity(rawContactId2, "iden1", "namespace1");
452
453        assertAggregated(rawContactId1, rawContactId2);
454    }
455
456    public void testAggregationExceptionKeepIn() {
457        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
458        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnk", "Smithk");
459
460        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
461        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnkx", "Smithkx");
462
463        long contactId1 = queryContactId(rawContactId1);
464        long contactId2 = queryContactId(rawContactId2);
465
466        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
467                rawContactId1, rawContactId2);
468
469        assertAggregated(rawContactId1, rawContactId2, "Johnkx Smithkx");
470
471        // Assert that the empty aggregate got removed
472        long newContactId1 = queryContactId(rawContactId1);
473        if (contactId1 != newContactId1) {
474            Cursor cursor = queryContact(contactId1);
475            assertFalse(cursor.moveToFirst());
476            cursor.close();
477        } else {
478            Cursor cursor = queryContact(contactId2);
479            assertFalse(cursor.moveToFirst());
480            cursor.close();
481        }
482    }
483
484    public void testAggregationExceptionKeepOut() {
485        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
486        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnh", "Smithh");
487
488        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
489        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnh", "Smithh");
490
491        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
492                rawContactId1, rawContactId2);
493
494        assertNotAggregated(rawContactId1, rawContactId2);
495    }
496
497    public void testAggregationExceptionKeepOutCheckUpdatesDisplayName() {
498        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
499        DataUtil.insertStructuredName(mResolver, rawContactId1, "Johni", "Smithi");
500
501        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
502        DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnj", "Smithj");
503
504        long rawContactId3 = RawContactUtil.createRawContact(mResolver, ACCOUNT_3);
505        DataUtil.insertStructuredName(mResolver, rawContactId3, "Johnm", "Smithm");
506
507        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
508                rawContactId1, rawContactId2);
509        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
510                rawContactId1, rawContactId3);
511        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
512                rawContactId2, rawContactId3);
513
514        assertAggregated(rawContactId1, rawContactId2, "Johnm Smithm");
515        assertAggregated(rawContactId1, rawContactId3);
516
517        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
518                rawContactId1, rawContactId2);
519        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
520                rawContactId1, rawContactId3);
521
522        assertNotAggregated(rawContactId1, rawContactId2);
523        assertNotAggregated(rawContactId1, rawContactId3);
524
525        String displayName1 = queryDisplayName(queryContactId(rawContactId1));
526        assertEquals("Johni Smithi", displayName1);
527
528        assertAggregated(rawContactId2, rawContactId3, "Johnm Smithm");
529
530        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
531                rawContactId2, rawContactId3);
532        assertNotAggregated(rawContactId1, rawContactId2);
533        assertNotAggregated(rawContactId1, rawContactId3);
534        assertNotAggregated(rawContactId2, rawContactId3);
535
536        String displayName2 = queryDisplayName(queryContactId(rawContactId1));
537        assertEquals("Johni Smithi", displayName2);
538
539        String displayName3 = queryDisplayName(queryContactId(rawContactId2));
540        assertEquals("Johnj Smithj", displayName3);
541
542        String displayName4 = queryDisplayName(queryContactId(rawContactId3));
543        assertEquals("Johnm Smithm", displayName4);
544    }
545
546    public void testAggregationExceptionKeepOutCheckResultDisplayNames() {
547        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "c", "c", ACCOUNT_1);
548        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "b", "b", ACCOUNT_2);
549        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "a", "a", ACCOUNT_3);
550
551        // Join all contacts
552        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
553                rawContactId1, rawContactId2);
554        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
555                rawContactId1, rawContactId3);
556        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
557                rawContactId2, rawContactId3);
558
559        // Separate all contacts. The order (2-3 , 1-2, 1-3) is important
560        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
561                rawContactId2, rawContactId3);
562        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
563                rawContactId1, rawContactId2);
564        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
565                rawContactId1, rawContactId3);
566
567        // Verify that we have three different contacts
568        long contactId1 = queryContactId(rawContactId1);
569        long contactId2 = queryContactId(rawContactId2);
570        long contactId3 = queryContactId(rawContactId3);
571
572        assertTrue(contactId1 != contactId2);
573        assertTrue(contactId1 != contactId3);
574        assertTrue(contactId2 != contactId3);
575
576        // Verify that each raw contact contribute to the contact display name
577        assertDisplayNameEquals(contactId1, rawContactId1);
578        assertDisplayNameEquals(contactId2, rawContactId2);
579        assertDisplayNameEquals(contactId3, rawContactId3);
580    }
581
582    public void testNonAggregationWithMultipleAffinities() {
583        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
584                ACCOUNT_1);
585        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
586                ACCOUNT_1);
587        assertNotAggregated(rawContactId1, rawContactId2);
588
589        // There are two aggregates this raw contact could join, so it should join neither
590        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
591                ACCOUNT_2);
592        assertNotAggregated(rawContactId1, rawContactId3);
593        assertNotAggregated(rawContactId2, rawContactId3);
594
595        // Just in case - let's make sure the original two did not get aggregated in the process
596        assertNotAggregated(rawContactId1, rawContactId2);
597    }
598
599    public void testReaggregateBecauseOfMultipleAffinities() {
600        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
601                ACCOUNT_1);
602        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
603                ACCOUNT_2);
604        assertAggregated(rawContactId1, rawContactId2);
605
606        // The aggregate this raw contact could join has a raw contact from the same account,
607        // The ambiguity will trigger re-aggregation. And since no data matching exists, all
608        // three raw contacts are broken-up.
609        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
610                ACCOUNT_1);
611        assertNotAggregated(rawContactId1, rawContactId3);
612        assertNotAggregated(rawContactId2, rawContactId3);
613        assertNotAggregated(rawContactId1, rawContactId2);
614    }
615
616    public void testAggregation_notAggregateByPhoneticName() {
617        // Different names, but have the same phonetic name.  Shouldn't be aggregated.
618
619        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
620        DataUtil.insertStructuredName(mResolver, rawContactId1, "Sergey", null, "Yamada");
621
622        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
623        DataUtil.insertStructuredName(mResolver, rawContactId2, "Lawrence", null, "Yamada");
624
625        assertNotAggregated(rawContactId1, rawContactId2);
626    }
627
628    public void testAggregation_notAggregateByPhoneticName_2() {
629        // Have the same phonetic name.  One has a regular name too.  Shouldn't be aggregated.
630
631        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
632        DataUtil.insertStructuredName(mResolver, rawContactId1, null, null, "Yamada");
633
634        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
635        DataUtil.insertStructuredName(mResolver, rawContactId2, "Lawrence", null, "Yamada");
636
637        assertNotAggregated(rawContactId1, rawContactId2);
638    }
639
640    public void testAggregation_PhoneticNameOnly() {
641        // If a contact has no name but a phonetic name, then its display will be set from the
642        // phonetic name.  In this case, we still aggregate by the display name.
643
644        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
645        DataUtil.insertStructuredName(mResolver, rawContactId1, null, null, "Yamada");
646
647        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
648        DataUtil.insertStructuredName(mResolver, rawContactId2, null, null, "Yamada");
649
650        assertAggregated(rawContactId1, rawContactId2, "Yamada");
651    }
652
653    public void testReaggregationWhenBecomesInvisible() {
654        Account account = new Account("accountName", "accountType");
655        createAutoAddGroup(account);
656
657        long rawContactId1 = RawContactUtil.createRawContact(mResolver, account);
658        DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder");
659
660        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
661        DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder");
662
663        long rawContactId3 = RawContactUtil.createRawContact(mResolver, account);
664        DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder");
665
666        assertNotAggregated(rawContactId1, rawContactId3);
667        assertNotAggregated(rawContactId2, rawContactId3);
668        assertNotAggregated(rawContactId1, rawContactId2);
669
670        // Hide by removing from all groups
671        removeGroupMemberships(rawContactId3);
672
673        assertAggregated(rawContactId1, rawContactId2, "Flynn Ryder");
674        assertNotAggregated(rawContactId1, rawContactId3);
675        assertNotAggregated(rawContactId2, rawContactId3);
676    }
677
678    public void testReaggregationWhenBecomesInvisibleSecondaryDataMatch() {
679        Account account = new Account("accountName", "accountType");
680        createAutoAddGroup(account);
681
682        long rawContactId1 = RawContactUtil.createRawContact(mResolver, account);
683        DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder");
684
685        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
686        DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder");
687
688        long rawContactId3 = RawContactUtil.createRawContact(mResolver, account);
689        DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder");
690
691        assertNotAggregated(rawContactId1, rawContactId3);
692        assertNotAggregated(rawContactId2, rawContactId3);
693        assertNotAggregated(rawContactId1, rawContactId2);
694
695        // Hide by removing from all groups
696        removeGroupMemberships(rawContactId3);
697
698        assertAggregated(rawContactId1, rawContactId2, "Flynn Ryder");
699        assertNotAggregated(rawContactId1, rawContactId3);
700        assertNotAggregated(rawContactId2, rawContactId3);
701    }
702
703    public void testReaggregationWhenBecomesVisible() {
704        Account account = new Account("accountName", "accountType");
705        long groupId = createAutoAddGroup(account);
706
707        long rawContactId1 = RawContactUtil.createRawContact(mResolver, account);
708        DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder");
709
710        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
711        DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder");
712
713        long rawContactId3 = RawContactUtil.createRawContact(mResolver, account);
714        removeGroupMemberships(rawContactId3);
715        DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder");
716
717        assertAggregated(rawContactId1, rawContactId2, "Flynn Ryder");
718        assertNotAggregated(rawContactId1, rawContactId3);
719        assertNotAggregated(rawContactId2, rawContactId3);
720
721        insertGroupMembership(rawContactId3, groupId);
722
723        assertNotAggregated(rawContactId1, rawContactId3);
724        assertNotAggregated(rawContactId2, rawContactId3);
725        assertNotAggregated(rawContactId1, rawContactId2);
726    }
727
728    public void testNonSplitBecauseOfMultipleAffinitiesWhenOverridden() {
729        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
730                ACCOUNT_1);
731        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
732                ACCOUNT_2);
733        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
734                ACCOUNT_3);
735        assertAggregated(rawContactId1, rawContactId2);
736        assertAggregated(rawContactId1, rawContactId3);
737        setAggregationException(
738                AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
739        assertAggregated(rawContactId1, rawContactId2);
740        assertAggregated(rawContactId1, rawContactId3);
741
742        // The aggregate this raw contact could join has a raw contact from the same account,
743        // Let's re-aggregate the existing aggregate because of the ambiguity
744        long rawContactId4 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
745                ACCOUNT_1);
746        assertAggregated(rawContactId1, rawContactId2);     // Aggregation exception
747        assertNotAggregated(rawContactId1, rawContactId3);
748        assertNotAggregated(rawContactId1, rawContactId4);
749        assertNotAggregated(rawContactId3, rawContactId4);
750    }
751
752    public void testNonSplitWhenIdentityMatch() {
753        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
754                ACCOUNT_1);
755        insertIdentity(rawContactId1, "iden", "namespace");
756        insertIdentity(rawContactId1, "iden2", "namespace");
757        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
758                ACCOUNT_2);
759        insertIdentity(rawContactId2, "iden", "namespace");
760        assertAggregated(rawContactId1, rawContactId2);
761
762        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
763                ACCOUNT_1);
764        assertAggregated(rawContactId1, rawContactId2);
765        assertNotAggregated(rawContactId1, rawContactId3);
766        assertNotAggregated(rawContactId2, rawContactId3);
767    }
768
769    public void testReAggregateToConnectedComponent() {
770        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
771                ACCOUNT_1);
772        insertPhoneNumber(rawContactId1, "111");
773        setRawContactCustomization(rawContactId1, 1, 1);
774        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
775                ACCOUNT_2);
776        insertPhoneNumber(rawContactId2, "111");
777        setRawContactCustomization(rawContactId2, 1, 1);
778        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
779                ACCOUNT_3);
780        insertIdentity(rawContactId3, "iden", "namespace");
781        long rawContactId4 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
782                new Account("account_name_4", "account_type_4"));
783        insertIdentity(rawContactId4, "iden", "namespace");
784
785        assertAggregated(rawContactId1, rawContactId2);
786        assertAggregated(rawContactId1, rawContactId3);
787        assertAggregated(rawContactId1, rawContactId4);
788        assertStoredValue(getContactUriForRawContact(rawContactId1),
789                Contacts.STARRED, 1);
790        assertStoredValue(getContactUriForRawContact(rawContactId4),
791                Contacts.SEND_TO_VOICEMAIL, 0);
792
793        long rawContactId5 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
794                ACCOUNT_1);
795
796        assertAggregated(rawContactId1, rawContactId2);
797        assertAggregated(rawContactId3, rawContactId4);
798        assertNotAggregated(rawContactId1, rawContactId3);
799        assertNotAggregated(rawContactId1, rawContactId5);
800        assertNotAggregated(rawContactId3, rawContactId5);
801        assertStoredValue(getContactUriForRawContact(rawContactId1),
802                Contacts.STARRED, 1);
803        assertStoredValue(getContactUriForRawContact(rawContactId1),
804                Contacts.SEND_TO_VOICEMAIL, 1);
805
806        assertStoredValue(getContactUriForRawContact(rawContactId3),
807                Contacts.STARRED, 0);
808        assertStoredValue(getContactUriForRawContact(rawContactId3),
809                Contacts.SEND_TO_VOICEMAIL, 0);
810
811        assertStoredValue(getContactUriForRawContact(rawContactId5),
812                Contacts.STARRED, 0);
813        assertStoredValue(getContactUriForRawContact(rawContactId5),
814                Contacts.SEND_TO_VOICEMAIL, 0);
815    }
816
817    public void testNonAggregationFromDifferentAccountWithIdentityMisMatch() {
818        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
819                ACCOUNT_1);
820        insertIdentity(rawContactId1, "iden1", "namespace");
821        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
822        insertIdentity(rawContactId2, "iden2", "namespace");
823        DataUtil.insertStructuredName(mResolver, rawContactId2, "John", "Doe");
824
825        // rawContact1 and rawContact2 have different identities on the same namespace,
826        // which prevent them to aggregate.
827        assertNotAggregated(rawContactId1, rawContactId2);
828    }
829
830    public void testNonAggregationFromSameAccountWithoutAnyDataMatching() {
831        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
832                ACCOUNT_1);
833        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
834                ACCOUNT_1);
835        assertNotAggregated(rawContactId1, rawContactId2);
836    }
837
838    public void testNonAggregationFromSameAccountNoCommonData() {
839        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
840                ACCOUNT_1);
841        insertEmail(rawContactId1, "lightning1@android.com");
842        insertPhoneNumber(rawContactId1, "111-222-3333");
843        insertIdentity(rawContactId1, "iden1", "namespace");
844
845        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
846                ACCOUNT_1);
847        insertEmail(rawContactId2, "lightning2@android.com");
848        insertPhoneNumber(rawContactId2, "555-666-7777");
849        insertIdentity(rawContactId1, "iden2", "namespace");
850
851        assertNotAggregated(rawContactId1, rawContactId2);
852    }
853
854    public void testAggregationFromSameAccountEmailSame_IgnoreCase() {
855        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
856                ACCOUNT_1);
857        insertEmail(rawContactId1, "lightning@android.com");
858
859        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
860                ACCOUNT_1);
861        insertEmail(rawContactId2, "lightning@android.com");
862
863        assertAggregated(rawContactId1, rawContactId2);
864
865        long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "Jane", "Doe",
866                ACCOUNT_1);
867        insertEmail(rawContactId3, "jane@android.com");
868
869        long rawContactId4 = RawContactUtil.createRawContactWithName(mResolver, "Jane", "Doe",
870                ACCOUNT_1);
871        insertEmail(rawContactId4, "JANE@ANDROID.COM");
872
873        assertAggregated(rawContactId3, rawContactId4);
874    }
875
876    public void testNonAggregationFromSameAccountEmailDifferent() {
877        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
878                ACCOUNT_1);
879        insertEmail(rawContactId1, "lightning1@android.com");
880
881        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
882                ACCOUNT_1);
883        insertEmail(rawContactId2, "lightning2@android.com");
884        insertEmail(rawContactId2, "lightning3@android.com");
885
886        assertNotAggregated(rawContactId1, rawContactId2);
887    }
888
889    public void testAggregationFromSameAccountIdentitySame() {
890        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
891                ACCOUNT_1);
892        insertIdentity(rawContactId1, "iden", "namespace");
893
894        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
895                ACCOUNT_1);
896        insertIdentity(rawContactId2, "iden", "namespace");
897
898        assertAggregated(rawContactId1, rawContactId2);
899    }
900
901    public void testNonAggregationFromSameAccountIdentityDifferent() {
902        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
903                ACCOUNT_1);
904        insertIdentity(rawContactId1, "iden1", "namespace1");
905        insertIdentity(rawContactId1, "iden2", "namespace2");
906
907        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
908                ACCOUNT_1);
909        insertIdentity(rawContactId2, "iden2", "namespace1");
910        insertIdentity(rawContactId2, "iden1", "namespace2");
911
912        assertNotAggregated(rawContactId1, rawContactId2);
913    }
914
915    public void testAggregationFromSameAccountPhoneNumberSame() {
916        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
917                ACCOUNT_1);
918        insertPhoneNumber(rawContactId1, "111-222-3333");
919
920        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
921                ACCOUNT_1);
922        insertPhoneNumber(rawContactId2, "111-222-3333");
923
924        assertAggregated(rawContactId1, rawContactId2);
925    }
926
927    public void testAggregationFromSameAccountPhoneNumberNormalizedSame() {
928        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
929                ACCOUNT_1);
930        insertPhoneNumber(rawContactId1, "111-222-3333");
931
932        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
933                ACCOUNT_1);
934        insertPhoneNumber(rawContactId2, "+1-111-222-3333");
935
936        assertAggregated(rawContactId1, rawContactId2);
937    }
938
939    public void testNonAggregationFromSameAccountPhoneNumberDifferent() {
940        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
941                ACCOUNT_1);
942        insertPhoneNumber(rawContactId1, "111-222-3333");
943
944        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe",
945                ACCOUNT_1);
946        insertPhoneNumber(rawContactId2, "111-222-3334");
947
948        assertNotAggregated(rawContactId1, rawContactId2);
949    }
950
951    public void testAggregationSuggestionsBasedOnName() {
952        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
953        DataUtil.insertStructuredName(mResolver, rawContactId1, "Duane", null);
954
955        // Exact name match
956        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
957        DataUtil.insertStructuredName(mResolver, rawContactId2, "Duane", null);
958        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
959                rawContactId1, rawContactId2);
960
961        // Edit distance == 0.84
962        long rawContactId3 = RawContactUtil.createRawContact(mResolver);
963        DataUtil.insertStructuredName(mResolver, rawContactId3, "Dwayne", null);
964
965        // Edit distance == 0.6
966        long rawContactId4 = RawContactUtil.createRawContact(mResolver);
967        DataUtil.insertStructuredName(mResolver, rawContactId4, "Donny", null);
968
969        long contactId1 = queryContactId(rawContactId1);
970        long contactId2 = queryContactId(rawContactId2);
971        long contactId3 = queryContactId(rawContactId3);
972
973        assertSuggestions(contactId1, contactId2, contactId3);
974    }
975
976    public void testAggregationSuggestionsBasedOnPhoneNumber() {
977
978        // Create two contacts that would not be aggregated because of name mismatch
979        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
980        DataUtil.insertStructuredName(mResolver, rawContactId1, "Lord", "Farquaad");
981        insertPhoneNumber(rawContactId1, "(888)555-1236");
982
983        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
984        DataUtil.insertStructuredName(mResolver, rawContactId2, "Talking", "Donkey");
985        insertPhoneNumber(rawContactId2, "1(888)555-1236");
986
987        long contactId1 = queryContactId(rawContactId1);
988        long contactId2 = queryContactId(rawContactId2);
989        assertTrue(contactId1 != contactId2);
990
991        assertSuggestions(contactId1, contactId2);
992    }
993
994    public void testAggregationSuggestionsBasedOnEmailAddress() {
995
996        // Create two contacts that would not be aggregated because of name mismatch
997        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
998        DataUtil.insertStructuredName(mResolver, rawContactId1, "Carl", "Fredricksen");
999        insertEmail(rawContactId1, "up@android.com");
1000
1001        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1002        DataUtil.insertStructuredName(mResolver, rawContactId2, "Charles", "Muntz");
1003        insertEmail(rawContactId2, "Up@Android.com");
1004
1005        long contactId1 = queryContactId(rawContactId1);
1006        long contactId2 = queryContactId(rawContactId2);
1007        assertTrue(contactId1 != contactId2);
1008
1009        assertSuggestions(contactId1, contactId2);
1010    }
1011
1012    public void testAggregationSuggestionsBasedOnEmailAddressApproximateMatch() {
1013
1014        // Create two contacts that would not be aggregated because of name mismatch
1015        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1016        DataUtil.insertStructuredName(mResolver, rawContactId1, "Bob", null);
1017        insertEmail(rawContactId1, "incredible@android.com");
1018
1019        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1020        DataUtil.insertStructuredName(mResolver, rawContactId2, "Lucius", "Best");
1021        insertEmail(rawContactId2, "incrediball@android.com");
1022
1023        long contactId1 = queryContactId(rawContactId1);
1024        long contactId2 = queryContactId(rawContactId2);
1025        assertTrue(contactId1 != contactId2);
1026
1027        assertSuggestions(contactId1, contactId2);
1028    }
1029
1030    public void testAggregationSuggestionsBasedOnNickname() {
1031        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1032        DataUtil.insertStructuredName(mResolver, rawContactId1, "Peter", "Parker");
1033        insertNickname(rawContactId1, "Spider-Man");
1034
1035        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1036        DataUtil.insertStructuredName(mResolver, rawContactId2, "Manny", "Spider");
1037
1038        long contactId1 = queryContactId(rawContactId1);
1039        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
1040                rawContactId1, rawContactId2);
1041
1042        long contactId2 = queryContactId(rawContactId2);
1043        assertSuggestions(contactId1, contactId2);
1044    }
1045
1046    public void testAggregationSuggestionsBasedOnNicknameMatchingName() {
1047        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1048        DataUtil.insertStructuredName(mResolver, rawContactId1, "Clark", "Kent");
1049        insertNickname(rawContactId1, "Superman");
1050
1051        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1052        DataUtil.insertStructuredName(mResolver, rawContactId2, "Roy", "Williams");
1053        insertNickname(rawContactId2, "superman");
1054
1055        long contactId1 = queryContactId(rawContactId1);
1056        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
1057                rawContactId1, rawContactId2);
1058
1059        long contactId2 = queryContactId(rawContactId2);
1060        assertSuggestions(contactId1, contactId2);
1061    }
1062
1063    public void testAggregationSuggestionsBasedOnCommonNickname() {
1064        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1065        DataUtil.insertStructuredName(mResolver, rawContactId1, "Dick", "Cherry");
1066
1067        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1068        DataUtil.insertStructuredName(mResolver, rawContactId2, "Richard", "Cherry");
1069
1070        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
1071                rawContactId1, rawContactId2);
1072
1073        long contactId1 = queryContactId(rawContactId1);
1074        long contactId2 = queryContactId(rawContactId2);
1075        assertSuggestions(contactId1, contactId2);
1076    }
1077
1078    public void testAggregationSuggestionsBasedOnPhoneNumberWithFilter() {
1079
1080        // Create two contacts that would not be aggregated because of name mismatch
1081        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1082        DataUtil.insertStructuredName(mResolver, rawContactId1, "Lord", "Farquaad");
1083        insertPhoneNumber(rawContactId1, "(888)555-1236");
1084
1085        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1086        DataUtil.insertStructuredName(mResolver, rawContactId2, "Talking", "Donkey");
1087        insertPhoneNumber(rawContactId2, "1(888)555-1236");
1088
1089        long contactId1 = queryContactId(rawContactId1);
1090        long contactId2 = queryContactId(rawContactId2);
1091        assertTrue(contactId1 != contactId2);
1092
1093        assertSuggestions(contactId1, "talk", contactId2);
1094        assertSuggestions(contactId1, "don", contactId2);
1095        assertSuggestions(contactId1, "", contactId2);
1096        assertSuggestions(contactId1, "eddie");
1097    }
1098
1099    public void testAggregationSuggestionsDontSuggestInvisible() {
1100        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "first", "last",
1101                ACCOUNT_1);
1102        insertPhoneNumber(rawContactId1, "111-222-3333");
1103        insertNickname(rawContactId1, "Superman");
1104        insertEmail(rawContactId1, "incredible@android.com");
1105
1106        // Create another with the exact same name, phone number, nickname and email.
1107        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "first", "last",
1108                ACCOUNT_2);
1109        insertPhoneNumber(rawContactId2, "111-222-3333");
1110        insertNickname(rawContactId2, "Superman");
1111        insertEmail(rawContactId2, "incredible@android.com");
1112
1113        // The aggregator should have joined them.  Split them up.
1114        setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
1115                rawContactId1, rawContactId2);
1116
1117        long contactId1 = queryContactId(rawContactId1);
1118        long contactId2 = queryContactId(rawContactId2);
1119
1120        // Make sure they're different contacts.
1121        MoreAsserts.assertNotEqual(contactId1, contactId2);
1122
1123        // Contact 2 should be suggested.
1124        assertSuggestions(contactId1, contactId2);
1125
1126        // Make contact 2 invisible.
1127        markInvisible(contactId2);
1128
1129        // Now contact 2 shuldn't be suggested.
1130        assertSuggestions(contactId1, new long[0]);
1131    }
1132
1133    public void testChoosePhotoSetBeforeAggregation() {
1134        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1135        setContactAccount(rawContactId1, "donut", "donut_act");
1136        insertPhoto(rawContactId1);
1137
1138        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1139        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1140        long cupcakeId = ContentUris.parseId(insertPhoto(rawContactId2));
1141
1142        long rawContactId3 = RawContactUtil.createRawContact(mResolver);
1143        setContactAccount(rawContactId3, "froyo", "froyo_act");
1144        insertPhoto(rawContactId3);
1145
1146        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1147                rawContactId1, rawContactId2);
1148        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1149                rawContactId1, rawContactId3);
1150        assertEquals(cupcakeId, queryPhotoId(queryContactId(rawContactId2)));
1151    }
1152
1153    public void testChoosePhotoSetAfterAggregation() {
1154        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1155        setContactAccount(rawContactId1, "donut", "donut_act");
1156        insertPhoto(rawContactId1);
1157
1158        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1159        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1160                rawContactId1, rawContactId2);
1161        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1162        long cupcakeId = ContentUris.parseId(insertPhoto(rawContactId2));
1163
1164        long rawContactId3 = RawContactUtil.createRawContact(mResolver);
1165        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1166                rawContactId1, rawContactId3);
1167        setContactAccount(rawContactId3, "froyo", "froyo_act");
1168        insertPhoto(rawContactId3);
1169
1170        assertEquals(cupcakeId, queryPhotoId(queryContactId(rawContactId2)));
1171    }
1172
1173    // Note that for the following tests of photo aggregation, the accounts are being used to
1174    // set the typical photo priority that each raw contact would have, based on
1175    // SynchronousContactsProvider2.createPhotoPriorityResolver.  The relative priorities
1176    // specified there are:
1177    // cupcake: 3
1178    // donut: 2
1179    // froyo: 1
1180    // <other>: 0
1181
1182    public void testChooseLargerPhotoByDimensions() {
1183        // Donut photo is 256x256.
1184        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1185        setContactAccount(rawContactId1, "donut", "donut_act");
1186        long normalEarthDataId = ContentUris.parseId(
1187                insertPhoto(rawContactId1, R.drawable.earth_normal));
1188        long normalEarthPhotoFileId = getStoredLongValue(
1189                ContentUris.withAppendedId(Data.CONTENT_URI, normalEarthDataId),
1190                Photo.PHOTO_FILE_ID);
1191
1192        // Cupcake would normally have priority, but its photo is 200x200.
1193        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1194        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1195        insertPhoto(rawContactId2, R.drawable.earth_200);
1196
1197        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1198                rawContactId1, rawContactId2);
1199
1200        // Larger photo (by dimensions) wins.
1201        assertEquals(normalEarthPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1)));
1202    }
1203
1204    public void testChooseLargerPhotoByFileSize() {
1205        // Donut photo is a 256x256 photo of a nebula.
1206        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1207        setContactAccount(rawContactId1, "donut", "donut_act");
1208        long nebulaDataId = ContentUris.parseId(
1209                insertPhoto(rawContactId1, R.drawable.nebula));
1210        long nebulaPhotoFileId = getStoredLongValue(
1211                ContentUris.withAppendedId(Data.CONTENT_URI, nebulaDataId),
1212                Photo.PHOTO_FILE_ID);
1213
1214        // Cupcake would normally have priority, but its photo (of a galaxy) has the same dimensions
1215        // as Donut's, but a smaller filesize.
1216        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1217        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1218        insertPhoto(rawContactId2, R.drawable.galaxy);
1219
1220        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1221                rawContactId1, rawContactId2);
1222
1223        // Larger photo (by filesize) wins.
1224        assertEquals(nebulaPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1)));
1225    }
1226
1227    public void testChooseFilePhotoOverThumbnail() {
1228        // Donut photo is a 256x256 photo of Earth.
1229        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1230        setContactAccount(rawContactId1, "donut", "donut_act");
1231        long normalEarthDataId = ContentUris.parseId(
1232                insertPhoto(rawContactId1, R.drawable.earth_normal));
1233        long normalEarthPhotoFileId = getStoredLongValue(
1234                ContentUris.withAppendedId(Data.CONTENT_URI, normalEarthDataId),
1235                Photo.PHOTO_FILE_ID);
1236
1237        // Cupcake would normally have priority, but its photo of Earth is thumbnail-sized.
1238        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1239        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1240        insertPhoto(rawContactId2, R.drawable.earth_small);
1241
1242        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1243                rawContactId1, rawContactId2);
1244
1245        // Larger photo (by filesize) wins.
1246        assertEquals(normalEarthPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1)));
1247    }
1248
1249    public void testFallbackToAccountPriorityForSamePhoto() {
1250        // Donut photo is a 256x256 photo of Earth.
1251        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1252        setContactAccount(rawContactId1, "donut", "donut_act");
1253        insertPhoto(rawContactId1, R.drawable.earth_normal);
1254
1255        // Cupcake has the same 256x256 photo of Earth.
1256        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1257        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1258        long cupcakeEarthDataId = ContentUris.parseId(
1259                insertPhoto(rawContactId2, R.drawable.earth_normal));
1260        long cupcakeEarthPhotoFileId = getStoredLongValue(
1261                ContentUris.withAppendedId(Data.CONTENT_URI, cupcakeEarthDataId),
1262                Photo.PHOTO_FILE_ID);
1263
1264        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1265                rawContactId1, rawContactId2);
1266
1267        // Cupcake's version of the photo wins, falling back to account priority.
1268        assertEquals(cupcakeEarthPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1)));
1269    }
1270
1271    public void testFallbackToAccountPriorityForDifferingThumbnails() {
1272        // Donut photo is a 96x96 thumbnail of Earth.
1273        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1274        setContactAccount(rawContactId1, "donut", "donut_act");
1275        insertPhoto(rawContactId1, R.drawable.earth_small);
1276
1277        // Cupcake photo is the 96x96 "no contact" placeholder (smaller filesize than the Earth
1278        // picture, but thumbnail filesizes are ignored in the aggregator).
1279        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1280        setContactAccount(rawContactId2, "cupcake", "cupcake_act");
1281        long cupcakeDataId = ContentUris.parseId(
1282                insertPhoto(rawContactId2, R.drawable.ic_contact_picture));
1283
1284        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
1285                rawContactId1, rawContactId2);
1286
1287        // The Cupcake thumbnail wins, by account priority..
1288        assertEquals(cupcakeDataId, queryPhotoId(queryContactId(rawContactId1)));
1289    }
1290
1291    public void testDisplayNameSources() {
1292        long rawContactId = RawContactUtil.createRawContact(mResolver);
1293        long contactId = queryContactId(rawContactId);
1294
1295        assertNull(queryDisplayName(contactId));
1296
1297        insertEmail(rawContactId, "eclair@android.com");
1298        assertEquals("eclair@android.com", queryDisplayName(contactId));
1299
1300        insertPhoneNumber(rawContactId, "800-555-5555");
1301        assertEquals("800-555-5555", queryDisplayName(contactId));
1302
1303        ContentValues values = new ContentValues();
1304        values.put(Organization.COMPANY, "Android");
1305        insertOrganization(rawContactId, values);
1306        assertEquals("Android", queryDisplayName(contactId));
1307
1308        insertNickname(rawContactId, "Dro");
1309        assertEquals("Dro", queryDisplayName(contactId));
1310
1311        values.clear();
1312        values.put(StructuredName.GIVEN_NAME, "Eclair");
1313        values.put(StructuredName.FAMILY_NAME, "Android");
1314        DataUtil.insertStructuredName(mResolver, rawContactId, values);
1315        assertEquals("Eclair Android", queryDisplayName(contactId));
1316    }
1317
1318    public void testMergeSuperPrimaryName_rawContact1() {
1319        // Setup: raw contact #1 has a super primary name. #2 doesn't.
1320        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1321        DataUtil.insertStructuredName(mResolver, rawContactId1, "name1", null, null,
1322                /* isSuperPrimary = */ true);
1323        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1324        DataUtil.insertStructuredName(mResolver, rawContactId2, "name2", null, null,
1325                /* isSuperPrimary = */ false);
1326
1327        // Action: aggregate
1328        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1,
1329                rawContactId2);
1330
1331        // Verify: the aggregate's name comes from raw contact #1
1332        long contactId = queryContactId(rawContactId1);
1333        assertEquals("name1", queryDisplayName(contactId));
1334    }
1335
1336    public void testMergeSuperPrimaryName_rawContact2AndEdit() {
1337        // Setup: raw contact #2 has a super primary name. #1 doesn't.
1338        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1339        final Uri nameUri1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1",
1340                null, null, /* isSuperPrimary = */ false);
1341        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1342        final Uri nameUri2 = DataUtil.insertStructuredName(mResolver, rawContactId2, "name2", null,
1343                null, /* isSuperPrimary = */ true);
1344
1345        // Action: aggregate
1346        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1,
1347                rawContactId2);
1348
1349        // Verify: the aggregate's name comes from raw contact #2. This is the opposite of the check
1350        // inside testMergeSuperPrimaryName_rawContact1().
1351        long contactId = queryContactId(rawContactId1);
1352        assertEquals("name2", queryDisplayName(contactId));
1353
1354        // Action: edit the super primary name
1355        final ContentValues values = new ContentValues();
1356        values.put(StructuredName.GIVEN_NAME, "edited name");
1357        mResolver.update(nameUri2, values, null, null);
1358
1359        // Verify: editing the super primary name affects aggregate name
1360        assertEquals("edited name", queryDisplayName(contactId));
1361
1362        // Action: edit the non primary name
1363        values.put(StructuredName.GIVEN_NAME, "edited name2");
1364        mResolver.update(nameUri1, values, null, null);
1365
1366        // Verify: aggregate name is still based off the primary name
1367        assertEquals("edited name", queryDisplayName(contactId));
1368    }
1369
1370    public void testMergedSuperPrimaryName_changeSuperPrimary() {
1371        // Setup: aggregated contact where raw contact #1 has a super primary name. #2 doesn't.
1372        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1373        final Uri nameUri1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1",
1374                null, null, /* isSuperPrimary = */ true);
1375        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1376        final Uri nameUri2 = DataUtil.insertStructuredName(mResolver, rawContactId2, "name2", null,
1377                null, /* isSuperPrimary = */ false);
1378        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1,
1379                rawContactId2);
1380
1381        // Action: make raw contact 2's name super primary
1382        storeValue(nameUri2, Data.IS_SUPER_PRIMARY, 1);
1383
1384        // Sanity check.
1385        assertStoredValue(nameUri1, Data.IS_SUPER_PRIMARY, 0);
1386        assertStoredValue(nameUri2, Data.IS_SUPER_PRIMARY, 1);
1387
1388        // Verify: aggregate name is based off of the newly super primary name
1389        long contactId = queryContactId(rawContactId1);
1390        assertEquals("name2", queryDisplayName(contactId));
1391    }
1392
1393    public void testAggregationModeSuspendedSeparateTransactions() {
1394
1395        // Setting aggregation mode to SUSPENDED should prevent aggregation from happening
1396        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1397        storeValue(RawContacts.CONTENT_URI, rawContactId1,
1398                RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
1399        Uri name1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "THE", "SAME");
1400
1401        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
1402        storeValue(RawContacts.CONTENT_URI, rawContactId2,
1403                RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
1404        DataUtil.insertStructuredName(mResolver, rawContactId2, "THE", "SAME");
1405
1406        assertNotAggregated(rawContactId1, rawContactId2);
1407
1408        // Changing aggregation mode to DEFAULT should change nothing
1409        storeValue(RawContacts.CONTENT_URI, rawContactId1,
1410                RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
1411        storeValue(RawContacts.CONTENT_URI, rawContactId2,
1412                RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
1413        assertNotAggregated(rawContactId1, rawContactId2);
1414
1415        // Changing the name should trigger aggregation
1416        storeValue(name1, StructuredName.GIVEN_NAME, "the");
1417        assertAggregated(rawContactId1, rawContactId2);
1418    }
1419
1420    public void testAggregationModeInitializedAsSuspended() throws Exception {
1421
1422        // Setting aggregation mode to SUSPENDED should prevent aggregation from happening
1423        ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
1424                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED)
1425                .build();
1426        ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
1427                .withValueBackReference(Data.RAW_CONTACT_ID, 0)
1428                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
1429                .withValue(StructuredName.GIVEN_NAME, "John")
1430                .withValue(StructuredName.FAMILY_NAME, "Doe")
1431                .build();
1432        ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
1433                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED)
1434                .build();
1435        ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
1436                .withValueBackReference(Data.RAW_CONTACT_ID, 2)
1437                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
1438                .withValue(StructuredName.GIVEN_NAME, "John")
1439                .withValue(StructuredName.FAMILY_NAME, "Doe")
1440                .build();
1441        ContentProviderOperation cpo5 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
1442                .withSelection(RawContacts._ID + "=?", new String[1])
1443                .withSelectionBackReference(0, 0)
1444                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT)
1445                .build();
1446        ContentProviderOperation cpo6 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
1447                .withSelection(RawContacts._ID + "=?", new String[1])
1448                .withSelectionBackReference(0, 2)
1449                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT)
1450                .build();
1451
1452        ContentProviderResult[] results =
1453                mResolver.applyBatch(ContactsContract.AUTHORITY,
1454                        Lists.newArrayList(cpo1, cpo2, cpo3, cpo4, cpo5, cpo6));
1455
1456        long rawContactId1 = ContentUris.parseId(results[0].uri);
1457        long rawContactId2 = ContentUris.parseId(results[2].uri);
1458
1459        assertNotAggregated(rawContactId1, rawContactId2);
1460    }
1461
1462    public void testAggregationModeUpdatedToSuspended() throws Exception {
1463
1464        // Setting aggregation mode to SUSPENDED should prevent aggregation from happening
1465        ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
1466                .withValues(new ContentValues())
1467                .build();
1468        ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
1469                .withValueBackReference(Data.RAW_CONTACT_ID, 0)
1470                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
1471                .withValue(StructuredName.GIVEN_NAME, "John")
1472                .withValue(StructuredName.FAMILY_NAME, "Doe")
1473                .build();
1474        ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
1475                .withValues(new ContentValues())
1476                .build();
1477        ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
1478                .withValueBackReference(Data.RAW_CONTACT_ID, 2)
1479                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
1480                .withValue(StructuredName.GIVEN_NAME, "John")
1481                .withValue(StructuredName.FAMILY_NAME, "Doe")
1482                .build();
1483        ContentProviderOperation cpo5 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
1484                .withSelection(RawContacts._ID + "=?", new String[1])
1485                .withSelectionBackReference(0, 0)
1486                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED)
1487                .build();
1488        ContentProviderOperation cpo6 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
1489                .withSelection(RawContacts._ID + "=?", new String[1])
1490                .withSelectionBackReference(0, 2)
1491                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED)
1492                .build();
1493
1494        ContentProviderResult[] results =
1495                mResolver.applyBatch(ContactsContract.AUTHORITY,
1496                        Lists.newArrayList(cpo1, cpo2, cpo3, cpo4, cpo5, cpo6));
1497
1498        long rawContactId1 = ContentUris.parseId(results[0].uri);
1499        long rawContactId2 = ContentUris.parseId(results[2].uri);
1500
1501        assertNotAggregated(rawContactId1, rawContactId2);
1502    }
1503
1504    public void testAggregationModeSuspendedOverriddenByAggException() throws Exception {
1505        ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
1506                .withValue(RawContacts.ACCOUNT_NAME, "a")
1507                .withValue(RawContacts.ACCOUNT_TYPE, "b")
1508                .build();
1509        ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
1510                .withValueBackReference(Data.RAW_CONTACT_ID, 0)
1511                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
1512                .withValue(StructuredName.GIVEN_NAME, "John")
1513                .withValue(StructuredName.FAMILY_NAME, "Doe")
1514                .build();
1515        ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
1516                .withValue(RawContacts.ACCOUNT_NAME, "c")
1517                .withValue(RawContacts.ACCOUNT_TYPE, "d")
1518                .build();
1519        ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
1520                .withValueBackReference(Data.RAW_CONTACT_ID, 2)
1521                .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
1522                .withValue(StructuredName.GIVEN_NAME, "John")
1523                .withValue(StructuredName.FAMILY_NAME, "Doe")
1524                .build();
1525        ContentProviderOperation cpo5 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
1526                .withSelection(RawContacts._ID + "=?", new String[1])
1527                .withSelectionBackReference(0, 0)
1528                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED)
1529                .build();
1530        ContentProviderOperation cpo6 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI)
1531                .withSelection(RawContacts._ID + "=?", new String[1])
1532                .withSelectionBackReference(0, 2)
1533                .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED)
1534                .build();
1535
1536        // Checking that aggregation mode SUSPENDED should be overridden by inserting
1537        // an explicit aggregation exception
1538        ContentProviderOperation cpo7 =
1539                ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI)
1540                .withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, 0)
1541                .withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, 2)
1542                .withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER)
1543                .build();
1544
1545        ContentProviderResult[] results =
1546                mResolver.applyBatch(ContactsContract.AUTHORITY,
1547                        Lists.newArrayList(cpo1, cpo2, cpo3, cpo4, cpo5, cpo6, cpo7));
1548
1549        long rawContactId1 = ContentUris.parseId(results[0].uri);
1550        long rawContactId2 = ContentUris.parseId(results[2].uri);
1551
1552        assertAggregated(rawContactId1, rawContactId2);
1553    }
1554
1555    public void testAggregationSuggestionsQueryBuilderWithContactId() throws Exception {
1556        Uri uri = AggregationSuggestions.builder().setContactId(12).setLimit(7).build();
1557        assertEquals("content://com.android.contacts/contacts/12/suggestions?limit=7",
1558                uri.toString());
1559    }
1560
1561    public void testAggregationSuggestionsQueryBuilderWithValues() throws Exception {
1562        Uri uri = AggregationSuggestions.builder()
1563                .addNameParameter("name1")
1564                .addNameParameter("name2")
1565                .setLimit(7)
1566                .build();
1567        assertEquals("content://com.android.contacts/contacts/0/suggestions?"
1568                + "limit=7"
1569                + "&query=name%3Aname1"
1570                + "&query=name%3Aname2", uri.toString());
1571    }
1572
1573    public void testAggregatedStatusUpdate() {
1574        long rawContactId1 = RawContactUtil.createRawContact(mResolver);
1575        Uri dataUri1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "john", "doe");
1576        insertStatusUpdate(ContentUris.parseId(dataUri1), StatusUpdates.AWAY, "Green", 100,
1577                StatusUpdates.CAPABILITY_HAS_CAMERA);
1578        long rawContactId2 = RawContactUtil.createRawContact(mResolver);
1579        Uri dataUri2 = DataUtil.insertStructuredName(mResolver, rawContactId2, "john", "doe");
1580        insertStatusUpdate(ContentUris.parseId(dataUri2), StatusUpdates.AVAILABLE, "Red", 50,
1581                StatusUpdates.CAPABILITY_HAS_CAMERA);
1582        setAggregationException(
1583                AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2);
1584
1585        assertStoredValue(getContactUriForRawContact(rawContactId1),
1586                Contacts.CONTACT_STATUS, "Green");
1587
1588        // When we split these two raw contacts, their respective statuses should be restored
1589        setAggregationException(
1590                AggregationExceptions.TYPE_KEEP_SEPARATE, rawContactId1, rawContactId2);
1591
1592        assertStoredValue(getContactUriForRawContact(rawContactId1),
1593                Contacts.CONTACT_STATUS, "Green");
1594
1595        assertStoredValue(getContactUriForRawContact(rawContactId2),
1596                Contacts.CONTACT_STATUS, "Red");
1597    }
1598
1599    public void testAggregationSuggestionsByName() throws Exception {
1600        long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "first1", "last1");
1601        long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "first2", "last2");
1602
1603        Uri uri = AggregationSuggestions.builder()
1604                .addNameParameter("last1 first1")
1605                .build();
1606
1607        Cursor cursor = mResolver.query(
1608                uri, new String[] { Contacts._ID, Contacts.DISPLAY_NAME }, null, null, null);
1609
1610        assertEquals(1, cursor.getCount());
1611
1612        cursor.moveToFirst();
1613
1614        ContentValues values = new ContentValues();
1615        values.put(Contacts._ID, queryContactId(rawContactId1));
1616        values.put(Contacts.DISPLAY_NAME, "first1 last1");
1617        assertCursorValues(cursor, values);
1618        cursor.close();
1619    }
1620
1621    public void testAggregation_clearSuperPrimary() {
1622        // Three types of mime-type super primary merging are tested here
1623        // 1. both raw contacts have super primary phone numbers
1624        // 2. both raw contacts have emails, but only one has super primary email
1625        // 3. only raw contact1 has organizations and it has set the super primary organization
1626        ContentValues values = new ContentValues();
1627        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1628        Uri uri_phone1a = insertPhoneNumber(rawContactId1, "(222)222-2222", true, true);
1629        Uri uri_phone1b = insertPhoneNumber(rawContactId1, "(555)555-5555", false, false);
1630        Uri uri_email1 = insertEmail(rawContactId1, "one@gmail.com", true, true);
1631        values.clear();
1632        values.put(Organization.COMPANY, "Monsters Inc");
1633        Uri uri_org1 = insertOrganization(rawContactId1, values, true, true);
1634        values.clear();
1635        values.put(Organization.TITLE, "CEO");
1636        Uri uri_org2 = insertOrganization(rawContactId1, values, false, false);
1637
1638        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2);
1639        Uri uri_phone2 = insertPhoneNumber(rawContactId2, "(333)333-3333", true, true);
1640        Uri uri_email2 = insertEmail(rawContactId2, "two@gmail.com", false, false);
1641
1642        // Two raw contacts with same phone number will trigger the aggregation
1643        Uri uri_phone3 = insertPhoneNumber(rawContactId1, "(111)111-1111", true, true);
1644        Uri uri_phone4 = insertPhoneNumber(rawContactId2, "1(111)111-1111", true, true);
1645
1646        // After aggregation, the super primary flag should only be cleared for case 1,
1647        // i.e., phone mime-type. Both case 2 and 3, i.e. organization and email mime-types,
1648        // have the super primary flag unchanged.
1649        assertAggregated(rawContactId1, rawContactId2);
1650        assertSuperPrimary(ContentUris.parseId(uri_phone1a), false);
1651        assertSuperPrimary(ContentUris.parseId(uri_phone1b), false);
1652        assertSuperPrimary(ContentUris.parseId(uri_phone2), false);
1653        assertSuperPrimary(ContentUris.parseId(uri_phone3), false);
1654        assertSuperPrimary(ContentUris.parseId(uri_phone4), false);
1655
1656        assertSuperPrimary(ContentUris.parseId(uri_email1), true);
1657        assertSuperPrimary(ContentUris.parseId(uri_email2), false);
1658
1659        assertSuperPrimary(ContentUris.parseId(uri_org1), true);
1660        assertSuperPrimary(ContentUris.parseId(uri_org2), false);
1661    }
1662
1663    public void testAggregation_clearSuperPrimarySingleMimetype() {
1664        // Setup: two raw contacts, each has a single name. One of the names is super primary.
1665        long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1666        long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1);
1667        final Uri uri = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1",
1668                null, null, /* isSuperPrimary = */ true);
1669
1670        // Sanity check.
1671        assertStoredValue(uri, Data.IS_SUPER_PRIMARY, 1);
1672
1673        // Action: aggregate
1674        setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1,
1675                rawContactId2);
1676
1677        // Verify: name is still super primary
1678        assertStoredValue(uri, Data.IS_SUPER_PRIMARY, 1);
1679    }
1680
1681    public void testNotAggregate_TooManyRawContactsInCandidate() {
1682        long preId= 0;
1683        for (int i = 0; i < ContactAggregator.AGGREGATION_CONTACT_SIZE_LIMIT; i++) {
1684            long id = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe");
1685            if (i >  0) {
1686                setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, preId, id);
1687            }
1688            preId = id;
1689        }
1690        // Although the newly added raw contact matches the names with other raw contacts,
1691        // but the best matching contact has already meets the size limit, so keep the new raw
1692        // contact separate from other raw contacts.
1693        long newId = RawContactUtil.createRawContact(mResolver,
1694                new Account("account_new", "new account type"));
1695        DataUtil.insertStructuredName(mResolver, newId, "John", "Doe");
1696        assertNotAggregated(preId, newId);
1697        assertTrue(queryContactId(newId) > 0);
1698    }
1699
1700    public void testFindConnectedRawContacts() {
1701        Set<Long> rawContactIdSet = new HashSet<Long>();
1702        rawContactIdSet.addAll(Arrays.asList(1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9l));
1703
1704        Multimap<Long, Long> matchingrawIdPairs = HashMultimap.create();
1705        matchingrawIdPairs.put(1l, 2l);
1706        matchingrawIdPairs.put(2l, 1l);
1707
1708        matchingrawIdPairs.put(1l, 7l);
1709        matchingrawIdPairs.put(7l, 1l);
1710
1711        matchingrawIdPairs.put(2l, 3l);
1712        matchingrawIdPairs.put(3l, 2l);
1713
1714        matchingrawIdPairs.put(2l, 8l);
1715        matchingrawIdPairs.put(8l, 2l);
1716
1717        matchingrawIdPairs.put(8l, 9l);
1718        matchingrawIdPairs.put(9l, 8l);
1719
1720        matchingrawIdPairs.put(4l, 5l);
1721        matchingrawIdPairs.put(5l, 4l);
1722
1723        Set<Set<Long>> actual = ContactAggregator.findConnectedComponents(rawContactIdSet,
1724                matchingrawIdPairs);
1725
1726        Set<Set<Long>> expected = new HashSet<Set<Long>>();
1727        Set<Long> result1 = new HashSet<Long>();
1728        result1.addAll(Arrays.asList(1l, 2l, 3l, 7l, 8l, 9l));
1729        Set<Long> result2 = new HashSet<Long>();
1730        result2.addAll(Arrays.asList(4l, 5l));
1731        Set<Long> result3 = new HashSet<Long>();
1732        result3.addAll(Arrays.asList(6l));
1733        expected.addAll(Arrays.asList(result1, result2, result3));
1734
1735        assertEquals(expected, actual);
1736    }
1737
1738    private void assertSuggestions(long contactId, long... suggestions) {
1739        final Uri aggregateUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1740        Uri uri = Uri.withAppendedPath(aggregateUri,
1741                Contacts.AggregationSuggestions.CONTENT_DIRECTORY);
1742        assertSuggestions(uri, suggestions);
1743    }
1744
1745    private void assertSuggestions(long contactId, String filter, long... suggestions) {
1746        final Uri aggregateUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId);
1747        Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(aggregateUri,
1748                Contacts.AggregationSuggestions.CONTENT_DIRECTORY), Uri.encode(filter));
1749        assertSuggestions(uri, suggestions);
1750    }
1751
1752    private void assertSuggestions(Uri uri, long... suggestions) {
1753        final Cursor cursor = mResolver.query(uri,
1754                new String[] { Contacts._ID, Contacts.CONTACT_PRESENCE },
1755                null, null, null);
1756
1757        try {
1758            assertEquals(suggestions.length, cursor.getCount());
1759
1760            for (int i = 0; i < suggestions.length; i++) {
1761                cursor.moveToNext();
1762                assertEquals(suggestions[i], cursor.getLong(0));
1763            }
1764        } finally {
1765            TestUtils.dumpCursor(cursor);
1766        }
1767
1768        cursor.close();
1769    }
1770
1771    private void assertDisplayNameEquals(long contactId, long rawContactId) {
1772
1773        String contactDisplayName = queryDisplayName(contactId);
1774
1775        Cursor c = queryRawContact(rawContactId);
1776        assertTrue(c.moveToFirst());
1777        String rawDisplayName = c.getString(c.getColumnIndex(RawContacts.DISPLAY_NAME_PRIMARY));
1778        c.close();
1779
1780        assertTrue(contactDisplayName != null);
1781        assertTrue(rawDisplayName != null);
1782        assertEquals(rawDisplayName, contactDisplayName);
1783    }
1784}
1785