diff options
4 files changed, 2000 insertions, 24 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 8fce6a6..c4da2e8 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -1534,6 +1534,18 @@ public class ContactsProvider2 extends AbstractContactsProvider return true; } + @VisibleForTesting + public void setNewAggregatorForTest(boolean enabled) { + mContactAggregator = (enabled) + ? new ContactAggregator2(this, mContactsHelper, + createPhotoPriorityResolver(getContext()), mNameSplitter, mCommonNicknameCache) + : new ContactAggregator(this, mContactsHelper, + createPhotoPriorityResolver(getContext()), mNameSplitter, mCommonNicknameCache); + mContactAggregator.setEnabled(SystemProperties.getBoolean(AGGREGATE_CONTACTS, true)); + initDataRowHandlers(mDataRowHandlers, mContactsHelper, mContactAggregator, + mContactsPhotoStore); + } + // Updates the locale set to reflect a new system locale. private static LocaleSet updateLocaleSet(LocaleSet oldLocales, Locale newLocale) { final Locale prevLocale = oldLocales.getPrimaryLocale(); diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java index d048609..f696950 100644 --- a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java +++ b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java @@ -147,9 +147,14 @@ public class ContactAggregator2 extends AbstractContactAggregator { RawContactMatcher matcher = new RawContactMatcher(); RawContactMatchingCandidates matchingCandidates = new RawContactMatchingCandidates(); if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) { - // Find the set of matching candidates - matchingCandidates = findRawContactMatchingCandidates(db, rawContactId, candidates, - matcher); + // If this is a newly inserted contact or a visible contact, look for + // data matches. + if (currentContactId == 0 + || mDbHelper.isContactInDefaultDirectory(db, currentContactId)) { + // Find the set of matching candidates + matchingCandidates = findRawContactMatchingCandidates(db, rawContactId, candidates, + matcher); + } } else if (aggregationMode == RawContacts.AGGREGATION_MODE_DISABLED) { return; } @@ -223,11 +228,10 @@ public class ContactAggregator2 extends AbstractContactAggregator { */ private RawContactMatchingCandidates findRawContactMatchingCandidates(SQLiteDatabase db, long rawContactId, MatchCandidateList candidates, RawContactMatcher matcher) { - updateMatchScoresForSuggestionsBasedOnDataMatches(db, rawContactId, candidates, + updateMatchScores(db, rawContactId, candidates, matcher); final RawContactMatchingCandidates matchingCandidates = new RawContactMatchingCandidates( - matcher.pickBestMatches(SCORE_THRESHOLD_SUGGEST)); - + matcher.pickBestMatches()); Set<Long> newIds = new HashSet<>(); newIds.addAll(matchingCandidates.getRawContactIdSet()); // Keep doing the following until no new raw contact candidate is found. @@ -236,9 +240,9 @@ public class ContactAggregator2 extends AbstractContactAggregator { final Set<Long> tmpIdSet = new HashSet<>(); for (long rId : newIds) { final RawContactMatcher rMatcher = new RawContactMatcher(); - updateMatchScoresForSuggestionsBasedOnDataMatches(db, rId, new MatchCandidateList(), + updateMatchScores(db, rId, new MatchCandidateList(), rMatcher); - List<MatchScore> newMatches = rMatcher.pickBestMatches(SCORE_THRESHOLD_SUGGEST); + List<MatchScore> newMatches = rMatcher.pickBestMatches(); for (MatchScore newMatch : newMatches) { final long newRawContactId = newMatch.getRawContactId(); if (!matchingCandidates.getRawContactIdSet().contains(newRawContactId)) { @@ -260,12 +264,10 @@ public class ContactAggregator2 extends AbstractContactAggregator { */ private void clearSuperPrimarySetting(SQLiteDatabase db, String rawContactIds) { final String sql = - "SELECT d." + DataColumns.MIMETYPE_ID + ", count(DISTINCT r." + - RawContacts.CONTACT_ID + ") c FROM " + Tables.DATA + " d JOIN " + - Tables.RAW_CONTACTS + " r on d." + Data.RAW_CONTACT_ID + " = r." + - RawContacts._ID +" WHERE d." + Data.IS_SUPER_PRIMARY + " = 1 AND d." + - Data.RAW_CONTACT_ID + " IN (" + rawContactIds + ") group by d." + - DataColumns.MIMETYPE_ID + " having c > 1"; + "SELECT " + DataColumns.MIMETYPE_ID + ", count(1) c FROM " + + Tables.DATA +" WHERE " + Data.IS_SUPER_PRIMARY + " = 1 AND " + + Data.RAW_CONTACT_ID + " IN (" + rawContactIds + ") group by " + + DataColumns.MIMETYPE_ID + " HAVING c > 1"; // Find out which mime-types exist with is_super_primary=true on more then one contacts. int index = 0; @@ -294,13 +296,12 @@ public class ContactAggregator2 extends AbstractContactAggregator { // in both raw contact of rawContactId and raw contacts of contactId String superPrimaryUpdateSql = "UPDATE " + Tables.DATA + " SET " + Data.IS_SUPER_PRIMARY + "=0" + - " WHERE (" + Data.RAW_CONTACT_ID + - " IN (SELECT " + RawContacts._ID + " FROM " + Tables.RAW_CONTACTS + - " WHERE " + Data.RAW_CONTACT_ID + " IN (" + rawContactIds + ")"; + " WHERE " + Data.RAW_CONTACT_ID + + " IN (" + rawContactIds + ")"; mimeTypeCondition.append(')'); superPrimaryUpdateSql += mimeTypeCondition.toString(); - db.execSQL(superPrimaryUpdateSql, null); + db.execSQL(superPrimaryUpdateSql); } private String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2, @@ -357,6 +358,12 @@ public class ContactAggregator2 extends AbstractContactAggregator { (currentContactContentsCount == 0) || canBeReused(db, currentCidForRawContact, connectedRawContactIds)) { contactId = currentCidForRawContact; + for (Long connectedRawContactId : connectedRawContactIds) { + Long cid = matchingCandidates.getContactId(connectedRawContactId); + if (cid != null && cid != contactId) { + cidsNeedToBeUpdated.add(cid); + } + } } else if (currentCidForRawContact != 0){ cidsNeedToBeUpdated.add(currentCidForRawContact); } @@ -373,8 +380,9 @@ public class ContactAggregator2 extends AbstractContactAggregator { } } } - createContactForRawContacts(db, txContext, connectedRawContactIds, contactId); clearSuperPrimarySetting(db, TextUtils.join(",", connectedRawContactIds)); + createContactForRawContacts(db, txContext, connectedRawContactIds, contactId); + for (Long cid : cidsNeedToBeUpdated) { long currentRcCount = 0; if (cid != 0) { @@ -850,12 +858,13 @@ public class ContactAggregator2 extends AbstractContactAggregator { } /** - * Computes scores for contacts that have matching data rows. + * Computes suggestion scores for contacts that have matching data rows. + * Aggregation suggestion doesn't consider aggregation exceptions, but is purely based on the + * raw contacts information. */ private void updateMatchScoresForSuggestionsBasedOnDataMatches(SQLiteDatabase db, long rawContactId, MatchCandidateList candidates, RawContactMatcher matcher) { - updateMatchScoresBasedOnExceptions(db, rawContactId, matcher); updateMatchScoresBasedOnIdentityMatch(db, rawContactId, matcher); updateMatchScoresBasedOnNameMatches(db, rawContactId, matcher); updateMatchScoresBasedOnEmailMatches(db, rawContactId, matcher); @@ -864,6 +873,19 @@ public class ContactAggregator2 extends AbstractContactAggregator { lookupApproximateNameMatches(db, candidates, matcher); } + /** + * Computes scores for contacts that have matching data rows. + */ + private void updateMatchScores(SQLiteDatabase db, long rawContactId, + MatchCandidateList candidates, RawContactMatcher matcher) { + updateMatchScoresBasedOnExceptions(db, rawContactId, matcher); + updateMatchScoresBasedOnIdentityMatch(db, rawContactId, matcher); + updateMatchScoresBasedOnNameMatches(db, rawContactId, matcher); + updateMatchScoresBasedOnEmailMatches(db, rawContactId, matcher); + updateMatchScoresBasedOnPhoneMatches(db, rawContactId, matcher); + updateMatchScoresBasedOnSecondaryData(db, rawContactId, candidates, matcher); + } + private void updateMatchScoresForSuggestionsBasedOnDataMatches(SQLiteDatabase db, MatchCandidateList candidates, RawContactMatcher matcher, ArrayList<AggregationSuggestionParameter> parameters) { @@ -875,4 +897,35 @@ public class ContactAggregator2 extends AbstractContactAggregator { // TODO: add support for other parameter kinds } } + + /** + * Update scores for matches with secondary data matching but insufficient primary scores. + * This method loads structured names for all candidate contacts and recomputes match scores + * using approximate matching. + */ + private void updateMatchScoresBasedOnSecondaryData(SQLiteDatabase db, + long rawContactId, MatchCandidateList candidates, RawContactMatcher matcher) { + final List<Long> secondaryRawContactIds = matcher.prepareSecondaryMatchCandidates(); + if (secondaryRawContactIds == null || secondaryRawContactIds.size() > SECONDARY_HIT_LIMIT) { + return; + } + + loadNameMatchCandidates(db, rawContactId, candidates, true); + + mSb.setLength(0); + mSb.append(RawContacts._ID).append(" IN ("); + for (int i = 0; i < secondaryRawContactIds.size(); i++) { + if (i != 0) { + mSb.append(','); + } + mSb.append(secondaryRawContactIds.get(i)); + } + + // We only want to compare structured names to structured names + // at this stage, we need to ignore all other sources of name lookup data. + mSb.append(") AND " + STRUCTURED_NAME_BASED_LOOKUP_SQL); + + matchAllCandidates(db, mSb.toString(), candidates, matcher, + RawContactMatcher.MATCHING_ALGORITHM_CONSERVATIVE, null); + } } diff --git a/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java b/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java index 1abcfa1..88aa226 100644 --- a/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java +++ b/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java @@ -43,9 +43,6 @@ public class RawContactMatcher { // and there is a secondary match (phone number, email etc). public static final int SCORE_THRESHOLD_SECONDARY = 50; - // Score for missing data (as opposed to present data but a bad match) - private static final int NO_DATA_SCORE = -1; - // Score for matching phone numbers private static final int PHONE_MATCH_SCORE = 71; @@ -280,6 +277,55 @@ public class RawContactMatcher { mScores.clear(); mScoreCount = 0; } + /** + * Returns a list of IDs for raw contacts that are matched on secondary data elements + * (phone number, email address, nickname). We still need to obtain the approximate + * primary score for those contacts to determine if any of them should be aggregated. + * <p> + * May return null. + */ + public List<Long> prepareSecondaryMatchCandidates() { + ArrayList<Long> rawContactIds = null; + + for (int i = 0; i < mScoreCount; i++) { + MatchScore score = mScoreList.get(i); + if (score.isKeepOut() || score.getPrimaryScore() > SCORE_THRESHOLD_PRIMARY){ + continue; + } + + if (score.getSecondaryScore() >= SCORE_THRESHOLD_PRIMARY) { + if (rawContactIds == null) { + rawContactIds = new ArrayList<Long>(); + } + rawContactIds.add(score.getRawContactId()); + } + score.setPrimaryScore(0); + } + return rawContactIds; + } + + /** + * Returns the list of raw contact Ids with the match score over threshold. + */ + public List<MatchScore> pickBestMatches() { + final List<MatchScore> matches = new ArrayList<>(); + for (int i = 0; i < mScoreCount; i++) { + MatchScore score = mScoreList.get(i); + if (score.isKeepOut()) { + continue; + } + + if (score.isKeepIn()) { + matches.add(score); + continue; + } + + if (score.getPrimaryScore() >= SCORE_THRESHOLD_SECONDARY) { + matches.add(score); + } + } + return matches; + } /** * Returns matches in the order of descending score. diff --git a/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java b/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java new file mode 100644 index 0000000..834b8f7 --- /dev/null +++ b/tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java @@ -0,0 +1,1865 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.contacts.aggregation; + +import android.accounts.Account; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.AggregationExceptions; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Contacts.AggregationSuggestions; +import android.provider.ContactsContract.Contacts.Photo; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.StatusUpdates; +import android.test.MoreAsserts; +import android.test.suitebuilder.annotation.MediumTest; + +import com.android.providers.contacts.BaseContactsProvider2Test; +import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns; +import com.android.providers.contacts.ContactsProvider2; +import com.android.providers.contacts.TestUtils; +import com.android.providers.contacts.tests.R; +import com.android.providers.contacts.testutil.DataUtil; +import com.android.providers.contacts.testutil.RawContactUtil; + +import com.google.android.collect.Lists; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Unit tests for {@link ContactAggregator2}. + * + * Run the test like this: + * <code> + * adb shell am instrument -e \ + * class com.android.providers.contacts.aggregation.ContactAggregator2Test -w \ + * com.android.providers.contacts.tests/android.test.InstrumentationTestRunner + * </code> + */ +@MediumTest +public class ContactAggregator2Test extends BaseContactsProvider2Test { + + private static final Account ACCOUNT_1 = new Account("account_name_1", "account_type_1"); + private static final Account ACCOUNT_2 = new Account("account_name_2", "account_type_2"); + private static final Account ACCOUNT_3 = new Account("account_name_3", "account_type_3"); + + private static final String[] AGGREGATION_EXCEPTION_PROJECTION = new String[] { + AggregationExceptions.TYPE, + AggregationExceptions.RAW_CONTACT_ID1, + AggregationExceptions.RAW_CONTACT_ID2 + }; + + protected void setUp() throws Exception { + super.setUp(); + // Enable new aggregator. + final ContactsProvider2 cp = (ContactsProvider2) getProvider(); + cp.setNewAggregatorForTest(true); + } + + public void testCrudAggregationExceptions() throws Exception { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "zz", "top"); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "aa", "bottom"); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + String selection = "(" + AggregationExceptions.RAW_CONTACT_ID1 + "=" + rawContactId1 + + " AND " + AggregationExceptions.RAW_CONTACT_ID2 + "=" + rawContactId2 + + ") OR (" + AggregationExceptions.RAW_CONTACT_ID1 + "=" + rawContactId2 + + " AND " + AggregationExceptions.RAW_CONTACT_ID2 + "=" + rawContactId1 + ")"; + + // Refetch the row we have just inserted + Cursor c = mResolver.query(AggregationExceptions.CONTENT_URI, + AGGREGATION_EXCEPTION_PROJECTION, selection, null, null); + + assertTrue(c.moveToFirst()); + assertEquals(AggregationExceptions.TYPE_KEEP_TOGETHER, c.getInt(0)); + assertTrue((rawContactId1 == c.getLong(1) && rawContactId2 == c.getLong(2)) + || (rawContactId2 == c.getLong(1) && rawContactId1 == c.getLong(2))); + assertFalse(c.moveToNext()); + c.close(); + + // Change from TYPE_KEEP_IN to TYPE_KEEP_OUT + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + c = mResolver.query(AggregationExceptions.CONTENT_URI, AGGREGATION_EXCEPTION_PROJECTION, + selection, null, null); + + assertTrue(c.moveToFirst()); + assertEquals(AggregationExceptions.TYPE_KEEP_SEPARATE, c.getInt(0)); + assertTrue((rawContactId1 == c.getLong(1) && rawContactId2 == c.getLong(2)) + || (rawContactId2 == c.getLong(1) && rawContactId1 == c.getLong(2))); + assertFalse(c.moveToNext()); + c.close(); + + // Delete the rule + setAggregationException(AggregationExceptions.TYPE_AUTOMATIC, + rawContactId1, rawContactId2); + + // Verify that the row is gone + c = mResolver.query(AggregationExceptions.CONTENT_URI, AGGREGATION_EXCEPTION_PROJECTION, + selection, null, null); + assertFalse(c.moveToFirst()); + c.close(); + } + + public void testAggregationCreatesNewAggregate() { + long rawContactId = RawContactUtil.createRawContact(mResolver); + + Uri resultUri = DataUtil.insertStructuredName(mResolver, rawContactId, "Johna", "Smitha"); + + // Parse the URI and confirm that it contains an ID + assertTrue(ContentUris.parseId(resultUri) != 0); + + long contactId = queryContactId(rawContactId); + assertTrue(contactId != 0); + + String displayName = queryDisplayName(contactId); + assertEquals("Johna Smitha", displayName); + } + + public void testAggregationOfExactFullNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnb", "Smithb"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnb", "Smithb"); + + assertAggregated(rawContactId1, rawContactId2, "Johnb Smithb"); + } + + public void testAggregationIgnoresInvisibleContact() { + Account account = new Account("accountName", "accountType"); + createAutoAddGroup(account); + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder"); + + // Hide by removing from all groups + removeGroupMemberships(rawContactId1); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder"); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder"); + + assertNotAggregated(rawContactId1, rawContactId2); + assertNotAggregated(rawContactId1, rawContactId3); + assertAggregated(rawContactId2, rawContactId3, "Flynn Ryder"); + } + + public void testAggregationOfCaseInsensitiveFullNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnc", "Smithc"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnc", "smithc"); + + assertAggregated(rawContactId1, rawContactId2, "Johnc Smithc"); + } + + public void testAggregationOfLastNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, null, "Johnd"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, null, "johnd"); + + assertAggregated(rawContactId1, rawContactId2, "Johnd"); + } + + public void testNonAggregationOfFirstNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johne", "Smithe"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johne", null); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testNonAggregationOfLastNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnf", "Smithf"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, null, "Smithf"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationOfConcatenatedFullNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johng", "Smithg"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "johngsmithg", null); + + assertAggregated(rawContactId1, rawContactId2, "Johng Smithg"); + } + + public void testAggregationOfNormalizedFullNameMatch() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "H\u00e9l\u00e8ne", "Bj\u00f8rn"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "helene bjorn", null); + + assertAggregated(rawContactId1, rawContactId2, "H\u00e9l\u00e8ne Bj\u00f8rn"); + } + + public void testAggregationOfNormalizedFullNameMatchWithReadOnlyAccount() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, new Account("acct", + READ_ONLY_ACCOUNT_TYPE)); + DataUtil.insertStructuredName(mResolver, rawContactId1, "H\u00e9l\u00e8ne", "Bj\u00f8rn"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "helene bjorn", null); + + assertAggregated(rawContactId1, rawContactId2, "helene bjorn"); + } + + public void testAggregationOfNumericNames() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "123", null); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "1-2-3", null); + + assertAggregated(rawContactId1, rawContactId2, "1-2-3"); + } + + public void testAggregationOfInconsistentlyParsedNames() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + + ContentValues values = new ContentValues(); + values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave"); + values.put(StructuredName.GIVEN_NAME, "604"); + values.put(StructuredName.MIDDLE_NAME, "Arizona"); + values.put(StructuredName.FAMILY_NAME, "Ave"); + DataUtil.insertStructuredName(mResolver, rawContactId1, values); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + values.clear(); + values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave"); + values.put(StructuredName.GIVEN_NAME, "604"); + values.put(StructuredName.FAMILY_NAME, "Arizona Ave"); + DataUtil.insertStructuredName(mResolver, rawContactId2, values); + + assertAggregated(rawContactId1, rawContactId2, "604 Arizona Ave"); + } + + public void testAggregationBasedOnMiddleName() { + ContentValues values = new ContentValues(); + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + values.put(StructuredName.GIVEN_NAME, "John"); + values.put(StructuredName.GIVEN_NAME, "Abigale"); + values.put(StructuredName.FAMILY_NAME, "James"); + + DataUtil.insertStructuredName(mResolver, rawContactId1, values); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + values.clear(); + values.put(StructuredName.GIVEN_NAME, "John"); + values.put(StructuredName.GIVEN_NAME, "Marie"); + values.put(StructuredName.FAMILY_NAME, "James"); + DataUtil.insertStructuredName(mResolver, rawContactId2, values); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnPhoneNumberNoNameData() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertPhoneNumber(rawContactId1, "(888)555-1231"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertPhoneNumber(rawContactId2, "1(888)555-1231"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnPhoneNumberWhenTargetAggregateHasNoName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertPhoneNumber(rawContactId1, "(888)555-1232"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnl", "Smithl"); + insertPhoneNumber(rawContactId2, "1(888)555-1232"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnPhoneNumberWhenNewContactHasNoName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnm", "Smithm"); + insertPhoneNumber(rawContactId1, "(888)555-1233"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertPhoneNumber(rawContactId2, "1(888)555-1233"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnPhoneNumberWithDifferentNames() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Baby", "Bear"); + insertPhoneNumber(rawContactId1, "(888)555-1235"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Blind", "Mouse"); + insertPhoneNumber(rawContactId2, "1(888)555-1235"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnPhoneNumberWithJustFirstName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Chick", "Notnull"); + insertPhoneNumber(rawContactId1, "(888)555-1236"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Chick", null); + insertPhoneNumber(rawContactId2, "1(888)555-1236"); + + assertAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnEmailNoNameData() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertEmail(rawContactId1, "lightning@android.com"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertEmail(rawContactId2, "lightning@android.com"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnEmailWhenTargetAggregateHasNoName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertEmail(rawContactId1, "mcqueen@android.com"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Lightning", "McQueen"); + insertEmail(rawContactId2, "mcqueen@android.com"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnEmailWhenNewContactHasNoName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Doc", "Hudson"); + insertEmail(rawContactId1, "doc@android.com"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertEmail(rawContactId2, "doc@android.com"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationBasedOnEmailWithDifferentNames() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Chick", "Hicks"); + insertEmail(rawContactId1, "hicky@android.com"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Luigi", "Guido"); + insertEmail(rawContactId2, "hicky@android.com"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationByCommonNicknameWithLastName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Bill", "Gore"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "William", "Gore"); + + assertAggregated(rawContactId1, rawContactId2, "William Gore"); + } + + public void testAggregationByCommonNicknameOnly() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Lawrence", null); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Larry", null); + + assertAggregated(rawContactId1, rawContactId2, "Lawrence"); + } + + public void testAggregationByNicknameNoStructuredName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertNickname(rawContactId1, "Frozone"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertNickname(rawContactId2, "Frozone"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationByNicknameWithDifferentNames() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Helen", "Parr"); + insertNickname(rawContactId1, "Elastigirl"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Shawn", "Johnson"); + insertNickname(rawContactId2, "Elastigirl"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testNonAggregationOnOrganization() { + ContentValues values = new ContentValues(); + values.put(Organization.TITLE, "Monsters, Inc"); + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertOrganization(rawContactId1, values); + insertNickname(rawContactId1, "Boo"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertOrganization(rawContactId2, values); + insertNickname(rawContactId2, "Rendall"); // To force reaggregation + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationByIdentity() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertIdentity(rawContactId1, "iden1", "namespace1"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + insertIdentity(rawContactId2, "iden1", "namespace1"); + + assertAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationExceptionKeepIn() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnk", "Smithk"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnkx", "Smithkx"); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + assertAggregated(rawContactId1, rawContactId2, "Johnkx Smithkx"); + + // Assert that the empty aggregate got removed + long newContactId1 = queryContactId(rawContactId1); + if (contactId1 != newContactId1) { + Cursor cursor = queryContact(contactId1); + assertFalse(cursor.moveToFirst()); + cursor.close(); + } else { + Cursor cursor = queryContact(contactId2); + assertFalse(cursor.moveToFirst()); + cursor.close(); + } + } + + public void testAggregationExceptionKeepOut() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johnh", "Smithh"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnh", "Smithh"); + + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationExceptionKeepOutCheckUpdatesDisplayName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Johni", "Smithi"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Johnj", "Smithj"); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver, ACCOUNT_3); + DataUtil.insertStructuredName(mResolver, rawContactId3, "Johnm", "Smithm"); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId3); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId2, rawContactId3); + + assertAggregated(rawContactId1, rawContactId2, "Johnm Smithm"); + assertAggregated(rawContactId1, rawContactId3); + + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId3); + + assertNotAggregated(rawContactId1, rawContactId2); + assertNotAggregated(rawContactId1, rawContactId3); + + String displayName1 = queryDisplayName(queryContactId(rawContactId1)); + assertEquals("Johni Smithi", displayName1); + + assertAggregated(rawContactId2, rawContactId3, "Johnm Smithm"); + + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId2, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId2); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + + String displayName2 = queryDisplayName(queryContactId(rawContactId1)); + assertEquals("Johni Smithi", displayName2); + + String displayName3 = queryDisplayName(queryContactId(rawContactId2)); + assertEquals("Johnj Smithj", displayName3); + + String displayName4 = queryDisplayName(queryContactId(rawContactId3)); + assertEquals("Johnm Smithm", displayName4); + } + + public void testAggregationExceptionKeepOutCheckResultDisplayNames() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "c", "c", ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "b", "b", ACCOUNT_2); + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "a", "a", ACCOUNT_3); + + // Join all contacts + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId3); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId2, rawContactId3); + + // Separate all contacts. The order (2-3 , 1-2, 1-3) is important + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId2, rawContactId3); + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId3); + + // Verify that we have three different contacts + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + long contactId3 = queryContactId(rawContactId3); + + assertTrue(contactId1 != contactId2); + assertTrue(contactId1 != contactId3); + assertTrue(contactId2 != contactId3); + + // Verify that each raw contact contribute to the contact display name + assertDisplayNameEquals(contactId1, rawContactId1); + assertDisplayNameEquals(contactId2, rawContactId2); + assertDisplayNameEquals(contactId3, rawContactId3); + } + + public void testNonAggregationWithMultipleAffinities() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + assertNotAggregated(rawContactId1, rawContactId2); + + // There are two aggregates this raw contact could join, so it should join neither + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_2); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + + // Just in case - let's make sure the original two did not get aggregated in the process + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testReaggregateBecauseOfMultipleAffinities() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_2); + assertAggregated(rawContactId1, rawContactId2); + + // The aggregate this raw contact could join has a raw contact from the same account, + // The ambiguity will trigger re-aggregation. And since no data matching exists, all + // three raw contacts are broken-up. + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregation_notAggregateByPhoneticName() { + // Different names, but have the same phonetic name. Shouldn't be aggregated. + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Sergey", null, "Yamada"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Lawrence", null, "Yamada"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregation_notAggregateByPhoneticName_2() { + // Have the same phonetic name. One has a regular name too. Shouldn't be aggregated. + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, null, null, "Yamada"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Lawrence", null, "Yamada"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregation_PhoneticNameOnly() { + // If a contact has no name but a phonetic name, then its display will be set from the + // phonetic name. In this case, we still aggregate by the display name. + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, null, null, "Yamada"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, null, null, "Yamada"); + + assertAggregated(rawContactId1, rawContactId2, "Yamada"); + } + + public void testReaggregationWhenBecomesInvisible() { + Account account = new Account("accountName", "accountType"); + createAutoAddGroup(account); + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder"); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder"); + + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId2); + + // Hide by removing from all groups + removeGroupMemberships(rawContactId3); + + assertAggregated(rawContactId1, rawContactId2, "Flynn Ryder"); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + } + + public void testReaggregationWhenBecomesInvisibleSecondaryDataMatch() { + Account account = new Account("accountName", "accountType"); + createAutoAddGroup(account); + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder"); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder"); + + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId2); + + // Hide by removing from all groups + removeGroupMemberships(rawContactId3); + + assertAggregated(rawContactId1, rawContactId2, "Flynn Ryder"); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + } + + public void testReaggregationWhenBecomesVisible() { + Account account = new Account("accountName", "accountType"); + long groupId = createAutoAddGroup(account); + + long rawContactId1 = RawContactUtil.createRawContact(mResolver, account); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Flynn", "Ryder"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Flynn", "Ryder"); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver, account); + removeGroupMemberships(rawContactId3); + DataUtil.insertStructuredName(mResolver, rawContactId3, "Flynn", "Ryder"); + + assertAggregated(rawContactId1, rawContactId2, "Flynn Ryder"); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + + insertGroupMembership(rawContactId3, groupId); + + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testNonSplitBecauseOfMultipleAffinitiesWhenOverridden() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_2); + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_3); + assertAggregated(rawContactId1, rawContactId2); + assertAggregated(rawContactId1, rawContactId3); + setAggregationException( + AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); + assertAggregated(rawContactId1, rawContactId2); + assertAggregated(rawContactId1, rawContactId3); + + // The aggregate this raw contact could join has a raw contact from the same account, + // Let's re-aggregate the existing aggregate because of the ambiguity + long rawContactId4 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + assertAggregated(rawContactId1, rawContactId2); // Aggregation exception + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId4); + assertNotAggregated(rawContactId3, rawContactId4); + } + + public void testNonSplitWhenIdentityMatch() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertIdentity(rawContactId1, "iden", "namespace"); + insertIdentity(rawContactId1, "iden2", "namespace"); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_2); + insertIdentity(rawContactId2, "iden", "namespace"); + assertAggregated(rawContactId1, rawContactId2); + + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + assertAggregated(rawContactId1, rawContactId2); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId3); + } + + public void testReAggregateToConnectedComponent() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId1, "111"); + setRawContactCustomization(rawContactId1, 1, 1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_2); + insertPhoneNumber(rawContactId2, "111"); + setRawContactCustomization(rawContactId2, 1, 1); + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_3); + insertIdentity(rawContactId3, "iden", "namespace"); + long rawContactId4 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + new Account("account_name_4", "account_type_4")); + insertIdentity(rawContactId4, "iden", "namespace"); + + assertAggregated(rawContactId1, rawContactId2); + assertAggregated(rawContactId1, rawContactId3); + assertAggregated(rawContactId1, rawContactId4); + assertStoredValue(getContactUriForRawContact(rawContactId1), + Contacts.STARRED, 1); + assertStoredValue(getContactUriForRawContact(rawContactId4), + Contacts.SEND_TO_VOICEMAIL, 0); + + long rawContactId5 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + + assertAggregated(rawContactId1, rawContactId2); + assertAggregated(rawContactId3, rawContactId4); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId5); + assertNotAggregated(rawContactId3, rawContactId5); + assertStoredValue(getContactUriForRawContact(rawContactId1), + Contacts.STARRED, 1); + assertStoredValue(getContactUriForRawContact(rawContactId1), + Contacts.SEND_TO_VOICEMAIL, 1); + + assertStoredValue(getContactUriForRawContact(rawContactId3), + Contacts.STARRED, 0); + assertStoredValue(getContactUriForRawContact(rawContactId3), + Contacts.SEND_TO_VOICEMAIL, 0); + + assertStoredValue(getContactUriForRawContact(rawContactId5), + Contacts.STARRED, 0); + assertStoredValue(getContactUriForRawContact(rawContactId5), + Contacts.SEND_TO_VOICEMAIL, 0); + } + + public void testNonAggregationFromSameAccountWithoutAnyDataMatching() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testNonAggregationFromSameAccountNoCommonData() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertEmail(rawContactId1, "lightning1@android.com"); + insertPhoneNumber(rawContactId1, "111-222-3333"); + insertIdentity(rawContactId1, "iden1", "namespace"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertEmail(rawContactId2, "lightning2@android.com"); + insertPhoneNumber(rawContactId2, "555-666-7777"); + insertIdentity(rawContactId1, "iden2", "namespace"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationFromSameAccountEmailSame_IgnoreCase() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertEmail(rawContactId1, "lightning@android.com"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertEmail(rawContactId2, "lightning@android.com"); + + assertAggregated(rawContactId1, rawContactId2); + + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "Jane", "Doe", + ACCOUNT_1); + insertEmail(rawContactId3, "jane@android.com"); + + long rawContactId4 = RawContactUtil.createRawContactWithName(mResolver, "Jane", "Doe", + ACCOUNT_1); + insertEmail(rawContactId4, "JANE@ANDROID.COM"); + + assertAggregated(rawContactId3, rawContactId4); + } + + public void testNonAggregationFromSameAccountEmailDifferent() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertEmail(rawContactId1, "lightning1@android.com"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertEmail(rawContactId2, "lightning2@android.com"); + insertEmail(rawContactId2, "lightning3@android.com"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationFromSameAccountIdentitySame() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertIdentity(rawContactId1, "iden", "namespace"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertIdentity(rawContactId2, "iden", "namespace"); + + assertAggregated(rawContactId1, rawContactId2); + } + + public void testNonAggregationFromSameAccountIdentityDifferent() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertIdentity(rawContactId1, "iden1", "namespace1"); + insertIdentity(rawContactId1, "iden2", "namespace2"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertIdentity(rawContactId2, "iden2", "namespace1"); + insertIdentity(rawContactId2, "iden1", "namespace2"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationFromSameAccountPhoneNumberSame() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId1, "111-222-3333"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId2, "111-222-3333"); + + assertAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationFromSameAccountPhoneNumberNormalizedSame() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId1, "111-222-3333"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId2, "+1-111-222-3333"); + + assertAggregated(rawContactId1, rawContactId2); + } + + public void testNonAggregationFromSameAccountPhoneNumberDifferent() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId1, "111-222-3333"); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + insertPhoneNumber(rawContactId2, "111-222-3334"); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationSuggestionsBasedOnName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Duane", null); + + // Exact name match + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Duane", null); + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + // Edit distance == 0.84 + long rawContactId3 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId3, "Dwayne", null); + + // Edit distance == 0.6 + long rawContactId4 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId4, "Donny", null); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + long contactId3 = queryContactId(rawContactId3); + + assertSuggestions(contactId1, contactId2, contactId3); + } + + public void testAggregationSuggestionsBasedOnPhoneNumber() { + + // Create two contacts that would not be aggregated because of name mismatch + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Lord", "Farquaad"); + insertPhoneNumber(rawContactId1, "(888)555-1236"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Talking", "Donkey"); + insertPhoneNumber(rawContactId2, "1(888)555-1236"); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + assertTrue(contactId1 != contactId2); + + assertSuggestions(contactId1, contactId2); + } + + public void testAggregationSuggestionsBasedOnEmailAddress() { + + // Create two contacts that would not be aggregated because of name mismatch + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Carl", "Fredricksen"); + insertEmail(rawContactId1, "up@android.com"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Charles", "Muntz"); + insertEmail(rawContactId2, "Up@Android.com"); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + assertTrue(contactId1 != contactId2); + + assertSuggestions(contactId1, contactId2); + } + + public void testAggregationSuggestionsBasedOnEmailAddressApproximateMatch() { + + // Create two contacts that would not be aggregated because of name mismatch + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Bob", null); + insertEmail(rawContactId1, "incredible@android.com"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Lucius", "Best"); + insertEmail(rawContactId2, "incrediball@android.com"); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + assertTrue(contactId1 != contactId2); + + assertSuggestions(contactId1, contactId2); + } + + public void testAggregationSuggestionsBasedOnNickname() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Peter", "Parker"); + insertNickname(rawContactId1, "Spider-Man"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Manny", "Spider"); + + long contactId1 = queryContactId(rawContactId1); + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + long contactId2 = queryContactId(rawContactId2); + assertSuggestions(contactId1, contactId2); + } + + public void testAggregationSuggestionsBasedOnNicknameMatchingName() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Clark", "Kent"); + insertNickname(rawContactId1, "Superman"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Roy", "Williams"); + insertNickname(rawContactId2, "superman"); + + long contactId1 = queryContactId(rawContactId1); + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + long contactId2 = queryContactId(rawContactId2); + assertSuggestions(contactId1, contactId2); + } + + public void testAggregationSuggestionsBasedOnCommonNickname() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Dick", "Cherry"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Richard", "Cherry"); + + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + assertSuggestions(contactId1, contactId2); + } + + public void testAggregationSuggestionsBasedOnPhoneNumberWithFilter() { + + // Create two contacts that would not be aggregated because of name mismatch + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId1, "Lord", "Farquaad"); + insertPhoneNumber(rawContactId1, "(888)555-1236"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Talking", "Donkey"); + insertPhoneNumber(rawContactId2, "1(888)555-1236"); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + assertTrue(contactId1 != contactId2); + + assertSuggestions(contactId1, "talk", contactId2); + assertSuggestions(contactId1, "don", contactId2); + assertSuggestions(contactId1, "", contactId2); + assertSuggestions(contactId1, "eddie"); + } + + public void testAggregationSuggestionsDontSuggestInvisible() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "first", "last", + ACCOUNT_1); + insertPhoneNumber(rawContactId1, "111-222-3333"); + insertNickname(rawContactId1, "Superman"); + insertEmail(rawContactId1, "incredible@android.com"); + + // Create another with the exact same name, phone number, nickname and email. + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "first", "last", + ACCOUNT_2); + insertPhoneNumber(rawContactId2, "111-222-3333"); + insertNickname(rawContactId2, "Superman"); + insertEmail(rawContactId2, "incredible@android.com"); + + // The aggregator should have joined them. Split them up. + setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE, + rawContactId1, rawContactId2); + + long contactId1 = queryContactId(rawContactId1); + long contactId2 = queryContactId(rawContactId2); + + // Make sure they're different contacts. + MoreAsserts.assertNotEqual(contactId1, contactId2); + + // Contact 2 should be suggested. + assertSuggestions(contactId1, contactId2); + + // Make contact 2 invisible. + markInvisible(contactId2); + + // Now contact 2 shuldn't be suggested. + assertSuggestions(contactId1, new long[0]); + } + + public void testChoosePhotoSetBeforeAggregation() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + insertPhoto(rawContactId1); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + long cupcakeId = ContentUris.parseId(insertPhoto(rawContactId2)); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId3, "froyo", "froyo_act"); + insertPhoto(rawContactId3); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId3); + assertEquals(cupcakeId, queryPhotoId(queryContactId(rawContactId2))); + } + + public void testChoosePhotoSetAfterAggregation() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + insertPhoto(rawContactId1); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + long cupcakeId = ContentUris.parseId(insertPhoto(rawContactId2)); + + long rawContactId3 = RawContactUtil.createRawContact(mResolver); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId3); + setContactAccount(rawContactId3, "froyo", "froyo_act"); + insertPhoto(rawContactId3); + + assertEquals(cupcakeId, queryPhotoId(queryContactId(rawContactId2))); + } + + // Note that for the following tests of photo aggregation, the accounts are being used to + // set the typical photo priority that each raw contact would have, based on + // SynchronousContactsProvider2.createPhotoPriorityResolver. The relative priorities + // specified there are: + // cupcake: 3 + // donut: 2 + // froyo: 1 + // <other>: 0 + + public void testChooseLargerPhotoByDimensions() { + // Donut photo is 256x256. + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + long normalEarthDataId = ContentUris.parseId( + insertPhoto(rawContactId1, R.drawable.earth_normal)); + long normalEarthPhotoFileId = getStoredLongValue( + ContentUris.withAppendedId(Data.CONTENT_URI, normalEarthDataId), + Photo.PHOTO_FILE_ID); + + // Cupcake would normally have priority, but its photo is 200x200. + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + insertPhoto(rawContactId2, R.drawable.earth_200); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + // Larger photo (by dimensions) wins. + assertEquals(normalEarthPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1))); + } + + public void testChooseLargerPhotoByFileSize() { + // Donut photo is a 256x256 photo of a nebula. + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + long nebulaDataId = ContentUris.parseId( + insertPhoto(rawContactId1, R.drawable.nebula)); + long nebulaPhotoFileId = getStoredLongValue( + ContentUris.withAppendedId(Data.CONTENT_URI, nebulaDataId), + Photo.PHOTO_FILE_ID); + + // Cupcake would normally have priority, but its photo (of a galaxy) has the same dimensions + // as Donut's, but a smaller filesize. + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + insertPhoto(rawContactId2, R.drawable.galaxy); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + // Larger photo (by filesize) wins. + assertEquals(nebulaPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1))); + } + + public void testChooseFilePhotoOverThumbnail() { + // Donut photo is a 256x256 photo of Earth. + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + long normalEarthDataId = ContentUris.parseId( + insertPhoto(rawContactId1, R.drawable.earth_normal)); + long normalEarthPhotoFileId = getStoredLongValue( + ContentUris.withAppendedId(Data.CONTENT_URI, normalEarthDataId), + Photo.PHOTO_FILE_ID); + + // Cupcake would normally have priority, but its photo of Earth is thumbnail-sized. + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + insertPhoto(rawContactId2, R.drawable.earth_small); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + // Larger photo (by filesize) wins. + assertEquals(normalEarthPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1))); + } + + public void testFallbackToAccountPriorityForSamePhoto() { + // Donut photo is a 256x256 photo of Earth. + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + insertPhoto(rawContactId1, R.drawable.earth_normal); + + // Cupcake has the same 256x256 photo of Earth. + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + long cupcakeEarthDataId = ContentUris.parseId( + insertPhoto(rawContactId2, R.drawable.earth_normal)); + long cupcakeEarthPhotoFileId = getStoredLongValue( + ContentUris.withAppendedId(Data.CONTENT_URI, cupcakeEarthDataId), + Photo.PHOTO_FILE_ID); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + // Cupcake's version of the photo wins, falling back to account priority. + assertEquals(cupcakeEarthPhotoFileId, queryPhotoFileId(queryContactId(rawContactId1))); + } + + public void testFallbackToAccountPriorityForDifferingThumbnails() { + // Donut photo is a 96x96 thumbnail of Earth. + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId1, "donut", "donut_act"); + insertPhoto(rawContactId1, R.drawable.earth_small); + + // Cupcake photo is the 96x96 "no contact" placeholder (smaller filesize than the Earth + // picture, but thumbnail filesizes are ignored in the aggregator). + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + setContactAccount(rawContactId2, "cupcake", "cupcake_act"); + long cupcakeDataId = ContentUris.parseId( + insertPhoto(rawContactId2, R.drawable.ic_contact_picture)); + + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, + rawContactId1, rawContactId2); + + // The Cupcake thumbnail wins, by account priority.. + assertEquals(cupcakeDataId, queryPhotoId(queryContactId(rawContactId1))); + } + + public void testDisplayNameSources() { + long rawContactId = RawContactUtil.createRawContact(mResolver); + long contactId = queryContactId(rawContactId); + + assertNull(queryDisplayName(contactId)); + + insertEmail(rawContactId, "eclair@android.com"); + assertEquals("eclair@android.com", queryDisplayName(contactId)); + + insertPhoneNumber(rawContactId, "800-555-5555"); + assertEquals("800-555-5555", queryDisplayName(contactId)); + + ContentValues values = new ContentValues(); + values.put(Organization.COMPANY, "Android"); + insertOrganization(rawContactId, values); + assertEquals("Android", queryDisplayName(contactId)); + + insertNickname(rawContactId, "Dro"); + assertEquals("Dro", queryDisplayName(contactId)); + + values.clear(); + values.put(StructuredName.GIVEN_NAME, "Eclair"); + values.put(StructuredName.FAMILY_NAME, "Android"); + DataUtil.insertStructuredName(mResolver, rawContactId, values); + assertEquals("Eclair Android", queryDisplayName(contactId)); + } + + public void testMergeSuperPrimaryName_rawContact1() { + // Setup: raw contact #1 has a super primary name. #2 doesn't. + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId1, "name1", null, null, + /* isSuperPrimary = */ true); + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertStructuredName(mResolver, rawContactId2, "name2", null, null, + /* isSuperPrimary = */ false); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: the aggregate's name comes from raw contact #1 + long contactId = queryContactId(rawContactId1); + assertEquals("name1", queryDisplayName(contactId)); + } + + public void testMergeSuperPrimaryName_rawContact2AndEdit() { + // Setup: raw contact #2 has a super primary name. #1 doesn't. + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + final Uri nameUri1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1", + null, null, /* isSuperPrimary = */ false); + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + final Uri nameUri2 = DataUtil.insertStructuredName(mResolver, rawContactId2, "name2", null, + null, /* isSuperPrimary = */ true); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: the aggregate's name comes from raw contact #2. This is the opposite of the check + // inside testMergeSuperPrimaryName_rawContact1(). + long contactId = queryContactId(rawContactId1); + assertEquals("name2", queryDisplayName(contactId)); + + // Action: edit the super primary name + final ContentValues values = new ContentValues(); + values.put(StructuredName.GIVEN_NAME, "edited name"); + mResolver.update(nameUri2, values, null, null); + + // Verify: editing the super primary name affects aggregate name + assertEquals("edited name", queryDisplayName(contactId)); + + // Action: edit the non primary name + values.put(StructuredName.GIVEN_NAME, "edited name2"); + mResolver.update(nameUri1, values, null, null); + + // Verify: aggregate name is still based off the primary name + assertEquals("edited name", queryDisplayName(contactId)); + } + + public void testMergedSuperPrimaryName_changeSuperPrimary() { + // Setup: aggregated contact where raw contact #1 has a super primary name. #2 doesn't. + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + final Uri nameUri1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1", + null, null, /* isSuperPrimary = */ true); + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + final Uri nameUri2 = DataUtil.insertStructuredName(mResolver, rawContactId2, "name2", null, + null, /* isSuperPrimary = */ false); + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Action: make raw contact 2's name super primary + storeValue(nameUri2, Data.IS_SUPER_PRIMARY, 1); + + // Sanity check. + assertStoredValue(nameUri1, Data.IS_SUPER_PRIMARY, 0); + assertStoredValue(nameUri2, Data.IS_SUPER_PRIMARY, 1); + + // Verify: aggregate name is based off of the newly super primary name + long contactId = queryContactId(rawContactId1); + assertEquals("name2", queryDisplayName(contactId)); + } + + public void testAggregationModeSuspendedSeparateTransactions() { + + // Setting aggregation mode to SUSPENDED should prevent aggregation from happening + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + storeValue(RawContacts.CONTENT_URI, rawContactId1, + RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); + Uri name1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "THE", "SAME"); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + storeValue(RawContacts.CONTENT_URI, rawContactId2, + RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED); + DataUtil.insertStructuredName(mResolver, rawContactId2, "THE", "SAME"); + + assertNotAggregated(rawContactId1, rawContactId2); + + // Changing aggregation mode to DEFAULT should change nothing + storeValue(RawContacts.CONTENT_URI, rawContactId1, + RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT); + storeValue(RawContacts.CONTENT_URI, rawContactId2, + RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT); + assertNotAggregated(rawContactId1, rawContactId2); + + // Changing the name should trigger aggregation + storeValue(name1, StructuredName.GIVEN_NAME, "the"); + assertAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationModeInitializedAsSuspended() throws Exception { + + // Setting aggregation mode to SUSPENDED should prevent aggregation from happening + ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) + .build(); + ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, 0) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.GIVEN_NAME, "John") + .withValue(StructuredName.FAMILY_NAME, "Doe") + .build(); + ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) + .build(); + ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, 2) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.GIVEN_NAME, "John") + .withValue(StructuredName.FAMILY_NAME, "Doe") + .build(); + ContentProviderOperation cpo5 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection(RawContacts._ID + "=?", new String[1]) + .withSelectionBackReference(0, 0) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT) + .build(); + ContentProviderOperation cpo6 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection(RawContacts._ID + "=?", new String[1]) + .withSelectionBackReference(0, 2) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT) + .build(); + + ContentProviderResult[] results = + mResolver.applyBatch(ContactsContract.AUTHORITY, + Lists.newArrayList(cpo1, cpo2, cpo3, cpo4, cpo5, cpo6)); + + long rawContactId1 = ContentUris.parseId(results[0].uri); + long rawContactId2 = ContentUris.parseId(results[2].uri); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationModeUpdatedToSuspended() throws Exception { + + // Setting aggregation mode to SUSPENDED should prevent aggregation from happening + ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValues(new ContentValues()) + .build(); + ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, 0) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.GIVEN_NAME, "John") + .withValue(StructuredName.FAMILY_NAME, "Doe") + .build(); + ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValues(new ContentValues()) + .build(); + ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, 2) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.GIVEN_NAME, "John") + .withValue(StructuredName.FAMILY_NAME, "Doe") + .build(); + ContentProviderOperation cpo5 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection(RawContacts._ID + "=?", new String[1]) + .withSelectionBackReference(0, 0) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) + .build(); + ContentProviderOperation cpo6 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection(RawContacts._ID + "=?", new String[1]) + .withSelectionBackReference(0, 2) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) + .build(); + + ContentProviderResult[] results = + mResolver.applyBatch(ContactsContract.AUTHORITY, + Lists.newArrayList(cpo1, cpo2, cpo3, cpo4, cpo5, cpo6)); + + long rawContactId1 = ContentUris.parseId(results[0].uri); + long rawContactId2 = ContentUris.parseId(results[2].uri); + + assertNotAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationModeSuspendedOverriddenByAggException() throws Exception { + ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValue(RawContacts.ACCOUNT_NAME, "a") + .withValue(RawContacts.ACCOUNT_TYPE, "b") + .build(); + ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, 0) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.GIVEN_NAME, "John") + .withValue(StructuredName.FAMILY_NAME, "Doe") + .build(); + ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) + .withValue(RawContacts.ACCOUNT_NAME, "c") + .withValue(RawContacts.ACCOUNT_TYPE, "d") + .build(); + ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI) + .withValueBackReference(Data.RAW_CONTACT_ID, 2) + .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE) + .withValue(StructuredName.GIVEN_NAME, "John") + .withValue(StructuredName.FAMILY_NAME, "Doe") + .build(); + ContentProviderOperation cpo5 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection(RawContacts._ID + "=?", new String[1]) + .withSelectionBackReference(0, 0) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) + .build(); + ContentProviderOperation cpo6 = ContentProviderOperation.newUpdate(RawContacts.CONTENT_URI) + .withSelection(RawContacts._ID + "=?", new String[1]) + .withSelectionBackReference(0, 2) + .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED) + .build(); + + // Checking that aggregation mode SUSPENDED should be overridden by inserting + // an explicit aggregation exception + ContentProviderOperation cpo7 = + ContentProviderOperation.newUpdate(AggregationExceptions.CONTENT_URI) + .withValueBackReference(AggregationExceptions.RAW_CONTACT_ID1, 0) + .withValueBackReference(AggregationExceptions.RAW_CONTACT_ID2, 2) + .withValue(AggregationExceptions.TYPE, AggregationExceptions.TYPE_KEEP_TOGETHER) + .build(); + + ContentProviderResult[] results = + mResolver.applyBatch(ContactsContract.AUTHORITY, + Lists.newArrayList(cpo1, cpo2, cpo3, cpo4, cpo5, cpo6, cpo7)); + + long rawContactId1 = ContentUris.parseId(results[0].uri); + long rawContactId2 = ContentUris.parseId(results[2].uri); + + assertAggregated(rawContactId1, rawContactId2); + } + + public void testAggregationSuggestionsQueryBuilderWithContactId() throws Exception { + Uri uri = AggregationSuggestions.builder().setContactId(12).setLimit(7).build(); + assertEquals("content://com.android.contacts/contacts/12/suggestions?limit=7", + uri.toString()); + } + + public void testAggregationSuggestionsQueryBuilderWithValues() throws Exception { + Uri uri = AggregationSuggestions.builder() + .addNameParameter("name1") + .addNameParameter("name2") + .setLimit(7) + .build(); + assertEquals("content://com.android.contacts/contacts/0/suggestions?" + + "limit=7" + + "&query=name%3Aname1" + + "&query=name%3Aname2", uri.toString()); + } + + public void testAggregatedStatusUpdate() { + long rawContactId1 = RawContactUtil.createRawContact(mResolver); + Uri dataUri1 = DataUtil.insertStructuredName(mResolver, rawContactId1, "john", "doe"); + insertStatusUpdate(ContentUris.parseId(dataUri1), StatusUpdates.AWAY, "Green", 100, + StatusUpdates.CAPABILITY_HAS_CAMERA); + long rawContactId2 = RawContactUtil.createRawContact(mResolver); + Uri dataUri2 = DataUtil.insertStructuredName(mResolver, rawContactId2, "john", "doe"); + insertStatusUpdate(ContentUris.parseId(dataUri2), StatusUpdates.AVAILABLE, "Red", 50, + StatusUpdates.CAPABILITY_HAS_CAMERA); + setAggregationException( + AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, rawContactId2); + + assertStoredValue(getContactUriForRawContact(rawContactId1), + Contacts.CONTACT_STATUS, "Green"); + + // When we split these two raw contacts, their respective statuses should be restored + setAggregationException( + AggregationExceptions.TYPE_KEEP_SEPARATE, rawContactId1, rawContactId2); + + assertStoredValue(getContactUriForRawContact(rawContactId1), + Contacts.CONTACT_STATUS, "Green"); + + assertStoredValue(getContactUriForRawContact(rawContactId2), + Contacts.CONTACT_STATUS, "Red"); + } + + public void testAggregationSuggestionsByName() throws Exception { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "first1", "last1"); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "first2", "last2"); + + Uri uri = AggregationSuggestions.builder() + .addNameParameter("last1 first1") + .build(); + + Cursor cursor = mResolver.query( + uri, new String[] { Contacts._ID, Contacts.DISPLAY_NAME }, null, null, null); + + assertEquals(1, cursor.getCount()); + + cursor.moveToFirst(); + + ContentValues values = new ContentValues(); + values.put(Contacts._ID, queryContactId(rawContactId1)); + values.put(Contacts.DISPLAY_NAME, "first1 last1"); + assertCursorValues(cursor, values); + cursor.close(); + } + + public void testAggregation_phoneticNamePriority1() { + // Setup: one raw contact has a complex phonetic name and the other a simple given name + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertPhoneticName(mResolver, rawContactId1, "name phonetic"); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, null, + "name", ACCOUNT_1); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: given name is used instead of phonetic, contrary to results of + // testAggregation_nameComplexity + long contactId = queryContactId(rawContactId1); + assertEquals("name", queryDisplayName(contactId)); + } + + // Same as testAggregation_phoneticNamePriority1, but with setup order reversed + public void testAggregation_phoneticNamePriority2() { + // Setup: one raw contact has a complex phonetic name and the other a simple given name + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, null, + "name", ACCOUNT_1); + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + DataUtil.insertPhoneticName(mResolver, rawContactId1, "name phonetic"); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: given name is used instead of phonetic, contrary to results of + // testAggregation_nameComplexity + long contactId = queryContactId(rawContactId1); + assertEquals("name", queryDisplayName(contactId)); + } + + public void testAggregation_nameComplexity1() { + // Setup: two names, one of which is unambiguously more complex + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, null, + "name", ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, null, + "name phonetic", ACCOUNT_1); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: more complex name is used + long contactId = queryContactId(rawContactId1); + assertEquals("name phonetic", queryDisplayName(contactId)); + } + + // Same as testAggregation_nameComplexity1, but with setup order reversed + public void testAggregation_nameComplexity2() { + // Setup: two names, one of which is unambiguously more complex + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, null, + "name phonetic", ACCOUNT_1); + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, null, + "name", ACCOUNT_1); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: more complex name is used + long contactId = queryContactId(rawContactId1); + assertEquals("name phonetic", queryDisplayName(contactId)); + } + + public void testAggregation_clearSuperPrimary() { + // Three types of mime-type super primary merging are tested here + // 1. both raw contacts have super primary phone numbers + // 2. both raw contacts have emails, but only one has super primary email + // 3. only raw contact1 has organizations and it has set the super primary organization + ContentValues values = new ContentValues(); + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + Uri uri_phone1a = insertPhoneNumber(rawContactId1, "(222)222-2222", true, true); + Uri uri_phone1b = insertPhoneNumber(rawContactId1, "(555)555-5555", false, false); + Uri uri_phone3 = insertPhoneNumber(rawContactId1, "(111)111-1111", true, true); + Uri uri_email1 = insertEmail(rawContactId1, "one@gmail.com", true, true); + values.clear(); + values.put(Organization.COMPANY, "Monsters Inc"); + Uri uri_org1 = insertOrganization(rawContactId1, values, true, true); + values.clear(); + values.put(Organization.TITLE, "CEO"); + Uri uri_org2 = insertOrganization(rawContactId1, values, false, false); + + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_2); + Uri uri_phone2 = insertPhoneNumber(rawContactId2, "(333)333-3333", true, true); + Uri uri_phone4 = insertPhoneNumber(rawContactId2, "1(111)111-1111", true, true); + Uri uri_email2 = insertEmail(rawContactId2, "two@gmail.com", false, false); + + // Two raw contacts with same name will trigger the aggregation + DataUtil.insertStructuredName(mResolver, rawContactId1, "Helen", "Parr"); + DataUtil.insertStructuredName(mResolver, rawContactId2, "Helen", "Parr"); + + // After aggregation, the super primary flag should only be cleared for case 1, + // i.e., phone mime-type. Both case 2 and 3, i.e. organization and email mime-types, + // have the super primary flag unchanged. + assertAggregated(rawContactId1, rawContactId2); + assertSuperPrimary(ContentUris.parseId(uri_phone1a), false); + assertSuperPrimary(ContentUris.parseId(uri_phone1b), false); + assertSuperPrimary(ContentUris.parseId(uri_phone2), false); + assertSuperPrimary(ContentUris.parseId(uri_phone3), false); + assertSuperPrimary(ContentUris.parseId(uri_phone4), false); + + assertSuperPrimary(ContentUris.parseId(uri_email1), true); + assertSuperPrimary(ContentUris.parseId(uri_email2), false); + + assertSuperPrimary(ContentUris.parseId(uri_org1), true); + assertSuperPrimary(ContentUris.parseId(uri_org2), false); + } + + public void testAggregation_clearSuperPrimarySingleMimetype() { + // Setup: two raw contacts, each has a single name. One of the names is super primary. + long rawContactId1 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + long rawContactId2 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + final Uri uri = DataUtil.insertStructuredName(mResolver, rawContactId1, "name1", + null, null, /* isSuperPrimary = */ true); + + // Sanity check. + assertStoredValue(uri, Data.IS_SUPER_PRIMARY, 1); + + // Action: aggregate + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1, + rawContactId2); + + // Verify: name is still super primary + assertStoredValue(uri, Data.IS_SUPER_PRIMARY, 1); + } + + public void testNotAggregate_TooManyRawContactsInCandidate() { + long preId= 0; + for (int i = 0; i < ContactAggregator.AGGREGATION_CONTACT_SIZE_LIMIT; i++) { + long id = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe"); + if (i > 0) { + setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, preId, id); + } + preId = id; + } + // Although the newly added raw contact matches the names with other raw contacts, + // but the best matching contact has already meets the size limit, so keep the new raw + // contact separate from other raw contacts. + long newId = RawContactUtil.createRawContact(mResolver, + new Account("account_new", "new account type")); + DataUtil.insertStructuredName(mResolver, newId, "John", "Doe"); + assertNotAggregated(preId, newId); + assertTrue(queryContactId(newId) > 0); + } + + public void testDoctorsWithSameWorkPhoneNumber() { + long rawContactId1 = RawContactUtil.createRawContactWithName(mResolver, "John", "Doe", + ACCOUNT_1); + ContentValues values = new ContentValues(); + values.put(Organization.TITLE, "Weight Management"); + insertOrganization(rawContactId1, values); + insertPhoneNumber(rawContactId1,"8296", false, 3); + insertPhoneNumber(rawContactId1,"8270", false, 4); + + long rawContactId2 = RawContactUtil.createRawContactWithName(mResolver, "Jane", "Smith", + ACCOUNT_1); + insertEmail(rawContactId2, "s@hospital.org"); + values.put(Organization.TITLE, "Weight Management Program"); + insertOrganization(rawContactId2, values); + insertPhoneNumber(rawContactId2,"8264", false, 3); + insertPhoneNumber(rawContactId2,"8270", false, 4); + + long rawContactId3 = RawContactUtil.createRawContactWithName(mResolver, "Karen", "Lee", + ACCOUNT_1); + insertEmail(rawContactId3, "l@hospital.org"); + values.put(Organization.TITLE, "Weight Management"); + insertOrganization(rawContactId3, values); + insertPhoneNumber(rawContactId3,"8268", false, 3); + insertPhoneNumber(rawContactId3,"8270", false, 4); + + long rawContactId4 = RawContactUtil.createRawContact(mResolver, ACCOUNT_1); + insertEmail(rawContactId4, "orders@v.org"); + values.put(Organization.TITLE, "Weight Management"); + insertOrganization(rawContactId4, values); + insertPhoneNumber(rawContactId4,"8262", false, 3); + insertPhoneNumber(rawContactId4,"8260", false, 3); + insertPhoneNumber(rawContactId4,"8270", false, 4); + + long rawContactId5 = RawContactUtil.createRawContactWithName(mResolver, "Rachel", "Fred", + ACCOUNT_1); + insertEmail(rawContactId5, "f@hospital.org"); + values.put(Organization.TITLE, "Weight Management"); + insertOrganization(rawContactId5, values); + insertPhoneNumber(rawContactId5,"8261", false, 3); + insertPhoneNumber(rawContactId5,"8270", false, 4); + + assertNotAggregated(rawContactId1, rawContactId2); + assertNotAggregated(rawContactId1, rawContactId3); + assertNotAggregated(rawContactId1, rawContactId4); + assertNotAggregated(rawContactId1, rawContactId5); + assertNotAggregated(rawContactId2, rawContactId3); + assertNotAggregated(rawContactId2, rawContactId4); + assertNotAggregated(rawContactId2, rawContactId5); + assertNotAggregated(rawContactId3, rawContactId4); + assertNotAggregated(rawContactId3, rawContactId5); + assertNotAggregated(rawContactId4, rawContactId5); + } + + private void assertSuggestions(long contactId, long... suggestions) { + final Uri aggregateUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + Uri uri = Uri.withAppendedPath(aggregateUri, + Contacts.AggregationSuggestions.CONTENT_DIRECTORY); + assertSuggestions(uri, suggestions); + } + + private void assertSuggestions(long contactId, String filter, long... suggestions) { + final Uri aggregateUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, contactId); + Uri uri = Uri.withAppendedPath(Uri.withAppendedPath(aggregateUri, + Contacts.AggregationSuggestions.CONTENT_DIRECTORY), Uri.encode(filter)); + assertSuggestions(uri, suggestions); + } + + private void assertSuggestions(Uri uri, long... suggestions) { + final Cursor cursor = mResolver.query(uri, + new String[] { Contacts._ID, Contacts.CONTACT_PRESENCE }, + null, null, null); + + try { + assertEquals(suggestions.length, cursor.getCount()); + + for (int i = 0; i < suggestions.length; i++) { + cursor.moveToNext(); + assertEquals(suggestions[i], cursor.getLong(0)); + } + } finally { + TestUtils.dumpCursor(cursor); + } + + cursor.close(); + } + + private void assertDisplayNameEquals(long contactId, long rawContactId) { + + String contactDisplayName = queryDisplayName(contactId); + + Cursor c = queryRawContact(rawContactId); + assertTrue(c.moveToFirst()); + String rawDisplayName = c.getString(c.getColumnIndex(RawContacts.DISPLAY_NAME_PRIMARY)); + c.close(); + + assertTrue(contactDisplayName != null); + assertTrue(rawDisplayName != null); + assertEquals(rawDisplayName, contactDisplayName); + } +} |