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