summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java12
-rw-r--r--src/com/android/providers/contacts/aggregation/ContactAggregator2.java95
-rw-r--r--src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java52
-rw-r--r--tests/src/com/android/providers/contacts/aggregation/ContactAggregator2Test.java1865
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);
+ }
+}