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