aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDmitri Plotnikov <dplotnikov@google.com>2010-10-11 15:23:53 -0700
committerDmitri Plotnikov <dplotnikov@google.com>2010-10-11 15:23:53 -0700
commit0dce6bf7a86a78d3073327419f17395c3a2d2688 (patch)
treeb6751ac8eaac08bd6b0b151996106b6d00a70f21
parent8638e1e999596ddfe12cbb529e58ca2345185af4 (diff)
downloadContactsProvider-0dce6bf7a86a78d3073327419f17395c3a2d2688.tar.gz
Changing contact aggregation to not join from same account
Second attempt, now w/o an infinite loop. The change addresses several requirements: 1. If "Michelle Lee" and "Michelle Lee" are two contacts in the same account, we won't aggregate them. 2. If "Michelle Lee" and "Michelle Lee" are two contacts from different accounts, they do get aggregated, but if a third "Michelle Lee" shows up in one of those two accounts - we bust the original aggregate. 3. If "Michelle Lee" and "Michelle Lee" are not aggregated and a third "Michelle Lee shows up, it does not get aggregated with either of the first two regardless of the account. 4. Any manual joining overrides the above behavior. 5. The OTA with this change will bust all aggregates that contain raw contacts from the same account. Bug: 2650610 Change-Id: I413231af4cfa620f8d266a758c22bfc879aeae07
-rw-r--r--src/com/android/providers/contacts/ContactAggregator.java188
-rw-r--r--src/com/android/providers/contacts/ContactMatcher.java16
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java66
-rw-r--r--tests/src/com/android/providers/contacts/ContactAggregatorTest.java196
4 files changed, 373 insertions, 93 deletions
diff --git a/src/com/android/providers/contacts/ContactAggregator.java b/src/com/android/providers/contacts/ContactAggregator.java
index 7404dca6..fb7995fe 100644
--- a/src/com/android/providers/contacts/ContactAggregator.java
+++ b/src/com/android/providers/contacts/ContactAggregator.java
@@ -331,11 +331,14 @@ public class ContactAggregator {
private interface AggregationQuery {
String SQL =
"SELECT " + RawContacts._ID + "," + RawContacts.CONTACT_ID +
+ ", " + RawContacts.ACCOUNT_TYPE + "," + RawContacts.ACCOUNT_NAME +
" FROM " + Tables.RAW_CONTACTS +
" WHERE " + RawContacts._ID + " IN(";
int _ID = 0;
int CONTACT_ID = 1;
+ int ACCOUNT_TYPE = 2;
+ int ACCOUNT_NAME = 3;
}
/**
@@ -372,6 +375,8 @@ public class ContactAggregator {
long rawContactIds[] = new long[count];
long contactIds[] = new long[count];
+ String accountTypes[] = new String[count];
+ String accountNames[] = new String[count];
Cursor c = db.rawQuery(mSb.toString(), selectionArgs);
try {
count = c.getCount();
@@ -379,6 +384,8 @@ public class ContactAggregator {
while (c.moveToNext()) {
rawContactIds[index] = c.getLong(AggregationQuery._ID);
contactIds[index] = c.getLong(AggregationQuery.CONTACT_ID);
+ accountTypes[index] = c.getString(AggregationQuery.ACCOUNT_TYPE);
+ accountNames[index] = c.getString(AggregationQuery.ACCOUNT_NAME);
index++;
}
} finally {
@@ -386,7 +393,8 @@ public class ContactAggregator {
}
for (int i = 0; i < count; i++) {
- aggregateContact(db, rawContactIds[i], contactIds[i], mCandidates, mMatcher, mValues);
+ aggregateContact(db, rawContactIds[i], accountTypes[i], accountNames[i], contactIds[i],
+ mCandidates, mMatcher, mValues);
}
long elapsedTime = System.currentTimeMillis() - start;
@@ -432,10 +440,44 @@ public class ContactAggregator {
mDbHelper.updateContactVisible(contactId);
}
+ private static final class RawContactIdAndAccountQuery {
+ public static final String TABLE = Tables.RAW_CONTACTS;
+
+ public static final String[] COLUMNS = {
+ RawContacts.CONTACT_ID, RawContacts.ACCOUNT_TYPE, RawContacts.ACCOUNT_NAME };
+
+ public static final String SELECTION = RawContacts._ID + "=?";
+
+ public static final int CONTACT_ID = 0;
+ public static final int ACCOUNT_TYPE = 1;
+ public static final int ACCOUNT_NAME = 2;
+ }
+
+ public void aggregateContact(SQLiteDatabase db, long rawContactId) {
+ long contactId = 0;
+ String accountName = null;
+ String accountType = null;
+ mSelectionArgs1[0] = String.valueOf(rawContactId);
+ Cursor cursor = db.query(RawContactIdAndAccountQuery.TABLE,
+ RawContactIdAndAccountQuery.COLUMNS, RawContactIdAndAccountQuery.SELECTION,
+ mSelectionArgs1, null, null, null);
+ try {
+ if (cursor.moveToFirst()) {
+ contactId = cursor.getLong(RawContactIdAndAccountQuery.CONTACT_ID);
+ accountType = cursor.getString(RawContactIdAndAccountQuery.ACCOUNT_TYPE);
+ accountName = cursor.getString(RawContactIdAndAccountQuery.ACCOUNT_NAME);
+ }
+ } finally {
+ cursor.close();
+ }
+ aggregateContact(db, rawContactId, accountType, accountName, contactId);
+ }
+
/**
* Synchronously aggregate the specified contact assuming an open transaction.
*/
- public void aggregateContact(SQLiteDatabase db, long rawContactId, long currentContactId) {
+ public void aggregateContact(SQLiteDatabase db, long rawContactId, String accountType,
+ String accountName, long currentContactId) {
if (!mEnabled) {
return;
}
@@ -444,7 +486,8 @@ public class ContactAggregator {
ContactMatcher matcher = new ContactMatcher();
ContentValues values = new ContentValues();
- aggregateContact(db, rawContactId, currentContactId, candidates, matcher, values);
+ aggregateContact(db, rawContactId, accountType, accountName, currentContactId, candidates,
+ matcher, values);
}
public void updateAggregateData(long contactId) {
@@ -472,8 +515,8 @@ public class ContactAggregator {
* with the highest match score. If no such contact is found, creates a new contact.
*/
private synchronized void aggregateContact(SQLiteDatabase db, long rawContactId,
- long currentContactId, MatchCandidateList candidates, ContactMatcher matcher,
- ContentValues values) {
+ String accountType, String accountName, long currentContactId,
+ MatchCandidateList candidates, ContactMatcher matcher, ContentValues values) {
int aggregationMode = RawContacts.AGGREGATION_MODE_DEFAULT;
@@ -513,22 +556,21 @@ public class ContactAggregator {
contactId = currentContactId;
}
+ long contactIdToSplit = -1;
+
+ if (contactId != currentContactId && contactId != -1) {
+ if (containsRawContactsFromAccount(db, contactId, accountType, accountName)) {
+ contactIdToSplit = contactId;
+ contactId = -1;
+ }
+ }
+
if (contactId == currentContactId) {
// Aggregation unchanged
markAggregated(rawContactId);
} else if (contactId == -1) {
// Splitting an aggregate
- mSelectionArgs1[0] = String.valueOf(rawContactId);
- computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1,
- mContactInsert);
- contactId = mContactInsert.executeInsert();
- setContactIdAndMarkAggregated(rawContactId, contactId);
- mDbHelper.updateContactVisible(contactId);
-
- setPresenceContactId(rawContactId, contactId);
-
- updateAggregatedPresence(contactId);
-
+ createNewContactForRawContact(db, rawContactId);
if (currentContactContentsCount > 0) {
updateAggregateData(currentContactId);
}
@@ -550,6 +592,106 @@ public class ContactAggregator {
mDbHelper.updateContactVisible(contactId);
updateAggregatedPresence(contactId);
}
+
+ if (contactIdToSplit != -1) {
+ splitAutomaticallyAggregatedRawContacts(db, contactIdToSplit);
+ }
+ }
+
+ /**
+ * Returns true if the aggregate contains has any raw contacts from the specified account.
+ */
+ private boolean containsRawContactsFromAccount(
+ SQLiteDatabase db, long contactId, String accountType, String accountName) {
+ String query;
+ String[] args;
+ if (accountType == null) {
+ query = "SELECT count(_id) FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + "=?" +
+ " AND " + RawContacts.ACCOUNT_TYPE + " IS NULL " +
+ " AND " + RawContacts.ACCOUNT_NAME + " IS NULL ";
+ args = mSelectionArgs1;
+ args[0] = String.valueOf(contactId);
+ } else {
+ query = "SELECT count(_id) FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + "=?" +
+ " AND " + RawContacts.ACCOUNT_TYPE + "=?" +
+ " AND " + RawContacts.ACCOUNT_NAME + "=?";
+ args = mSelectionArgs3;
+ args[0] = String.valueOf(contactId);
+ args[1] = accountType;
+ args[2] = accountName;
+ }
+ Cursor cursor = db.rawQuery(query, args);
+ try {
+ cursor.moveToFirst();
+ return cursor.getInt(0) != 0;
+ } finally {
+ cursor.close();
+ }
+ }
+
+ /**
+ * Breaks up an existing aggregate when a new raw contact is inserted that has
+ * comes from the same account as one of the raw contacts in this aggregate.
+ */
+ private void splitAutomaticallyAggregatedRawContacts(SQLiteDatabase db, long contactId) {
+ mSelectionArgs1[0] = String.valueOf(contactId);
+ int count = (int) DatabaseUtils.longForQuery(db,
+ "SELECT COUNT(" + RawContacts._ID + ")" +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + "=?", mSelectionArgs1);
+ if (count < 2) {
+ // A single-raw-contact aggregate does not need to be split up
+ return;
+ }
+
+ // Find all constituent raw contacts that are not held together by
+ // an explicit aggregation exception
+ String query =
+ "SELECT " + RawContacts._ID +
+ " FROM " + Tables.RAW_CONTACTS +
+ " WHERE " + RawContacts.CONTACT_ID + "=?" +
+ " AND " + RawContacts._ID + " NOT IN " +
+ "(SELECT " + AggregationExceptions.RAW_CONTACT_ID1 +
+ " FROM " + Tables.AGGREGATION_EXCEPTIONS +
+ " WHERE " + AggregationExceptions.TYPE + "="
+ + AggregationExceptions.TYPE_KEEP_TOGETHER +
+ " UNION SELECT " + AggregationExceptions.RAW_CONTACT_ID2 +
+ " FROM " + Tables.AGGREGATION_EXCEPTIONS +
+ " WHERE " + AggregationExceptions.TYPE + "="
+ + AggregationExceptions.TYPE_KEEP_TOGETHER +
+ ")";
+ Cursor cursor = db.rawQuery(query, mSelectionArgs1);
+ try {
+ // Process up to count-1 raw contact, leaving the last one alone.
+ for (int i = 0; i < count - 1; i++) {
+ if (!cursor.moveToNext()) {
+ break;
+ }
+ long rawContactId = cursor.getLong(0);
+ createNewContactForRawContact(db, rawContactId);
+ }
+ } finally {
+ cursor.close();
+ }
+ if (contactId > 0) {
+ updateAggregateData(contactId);
+ }
+ }
+
+ /**
+ * Creates a stand-alone Contact for the given raw contact ID.
+ */
+ private void createNewContactForRawContact(SQLiteDatabase db, long rawContactId) {
+ mSelectionArgs1[0] = String.valueOf(rawContactId);
+ computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1,
+ mContactInsert);
+ long contactId = mContactInsert.executeInsert();
+ setContactIdAndMarkAggregated(rawContactId, contactId);
+ mDbHelper.updateContactVisible(contactId);
+ setPresenceContactId(rawContactId, contactId);
+ updateAggregatedPresence(contactId);
}
/**
@@ -704,7 +846,7 @@ public class ContactAggregator {
c.close();
}
- return matcher.pickBestMatch(ContactMatcher.MAX_SCORE);
+ return matcher.pickBestMatch(ContactMatcher.MAX_SCORE, true);
}
/**
@@ -726,9 +868,15 @@ public class ContactAggregator {
// Find good matches based on name alone
long bestMatch = updateMatchScoresBasedOnDataMatches(db, rawContactId, candidates, matcher);
- if (bestMatch == -1) {
+ if (bestMatch == ContactMatcher.MULTIPLE_MATCHES) {
+ // We found multiple matches on the name - do not aggregate because of the ambiguity
+ return -1;
+ } else if (bestMatch == -1) {
// We haven't found a good match on name, see if we have any matches on phone, email etc
bestMatch = pickBestMatchBasedOnSecondaryData(db, rawContactId, candidates, matcher);
+ if (bestMatch == ContactMatcher.MULTIPLE_MATCHES) {
+ return -1;
+ }
}
return bestMatch;
@@ -766,7 +914,7 @@ public class ContactAggregator {
matchAllCandidates(db, mSb.toString(), candidates, matcher,
ContactMatcher.MATCHING_ALGORITHM_CONSERVATIVE, null);
- return matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_SECONDARY);
+ return matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_SECONDARY, false);
}
private interface NameLookupQuery {
@@ -812,7 +960,7 @@ public class ContactAggregator {
MatchCandidateList candidates, ContactMatcher matcher) {
updateMatchScoresBasedOnNameMatches(db, rawContactId, matcher);
- long bestMatch = matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_PRIMARY);
+ long bestMatch = matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_PRIMARY, false);
if (bestMatch != -1) {
return bestMatch;
}
diff --git a/src/com/android/providers/contacts/ContactMatcher.java b/src/com/android/providers/contacts/ContactMatcher.java
index a38f2760..7f26d903 100644
--- a/src/com/android/providers/contacts/ContactMatcher.java
+++ b/src/com/android/providers/contacts/ContactMatcher.java
@@ -68,6 +68,9 @@ public class ContactMatcher {
// Minimum edit distance between two email ids to be considered an approximate match
public static final float APPROXIMATE_MATCH_THRESHOLD_FOR_EMAIL = 0.95f;
+ // Returned value when we found multiple matches and that was not allowed
+ public static final long MULTIPLE_MATCHES = -2;
+
/**
* Name matching scores: a matrix by name type vs. candidate lookup type.
* For example, if the name type is "full name" while we are looking for a
@@ -371,7 +374,7 @@ public class ContactMatcher {
* Returns the contactId with the best match score over the specified threshold or -1
* if no such contact is found.
*/
- public long pickBestMatch(int threshold) {
+ public long pickBestMatch(int threshold, boolean allowMultipleMatches) {
long contactId = -1;
int maxScore = 0;
for (int i = 0; i < mScoreCount; i++) {
@@ -389,9 +392,14 @@ public class ContactMatcher {
s = score.mSecondaryScore;
}
- if (s >= threshold && s > maxScore) {
- contactId = score.mContactId;
- maxScore = s;
+ if (s >= threshold) {
+ if (contactId != -1 && !allowMultipleMatches) {
+ return MULTIPLE_MATCHES;
+ }
+ if (s > maxScore) {
+ contactId = score.mContactId;
+ maxScore = s;
+ }
}
}
return contactId;
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index a16d3cda..61a67d4e 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -76,6 +76,7 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.os.MemoryFile;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.pim.vcard.VCardComposer;
import android.pim.vcard.VCardConfig;
@@ -156,6 +157,9 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
private static final int PROPERTY_CONTACTS_IMPORT_VERSION = 1;
private static final String PREF_LOCALE = "locale";
+ private static final String PROPERTY_AGGREGATION_ALGORITHM = "aggregation_v2";
+ private static final int PROPERTY_AGGREGATION_ALGORITHM_VERSION = 2;
+
private static final String AGGREGATE_CONTACTS = "sync.contacts.aggregate";
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
@@ -1995,6 +1999,10 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
verifyLocale();
}
+ if (isAggregationUpgradeNeeded()) {
+ upgradeAggregationAlgorithm();
+ }
+
return (mDb != null);
}
@@ -2634,8 +2642,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
case RawContacts.AGGREGATION_MODE_IMMEDIATE: {
- long contactId = mDbHelper.getContactId(rawContactId);
- mContactAggregator.aggregateContact(mDb, rawContactId, contactId);
+ mContactAggregator.aggregateContact(mDb, rawContactId);
break;
}
}
@@ -3963,11 +3970,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
mContactAggregator.markForAggregation(rawContactId2,
RawContacts.AGGREGATION_MODE_DEFAULT, true);
- long contactId1 = mDbHelper.getContactId(rawContactId1);
- mContactAggregator.aggregateContact(db, rawContactId1, contactId1);
-
- long contactId2 = mDbHelper.getContactId(rawContactId2);
- mContactAggregator.aggregateContact(db, rawContactId2, contactId2);
+ mContactAggregator.aggregateContact(db, rawContactId1);
+ mContactAggregator.aggregateContact(db, rawContactId2);
// The return value is fake - we just confirm that we made a change, not count actual
// rows changed.
@@ -6014,4 +6018,52 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
stmt.bindLong(index, value.longValue());
}
}
+
+ protected boolean isAggregationUpgradeNeeded() {
+ if (!mContactAggregator.isEnabled()) {
+ return false;
+ }
+
+ int version = Integer.parseInt(mDbHelper.getProperty(PROPERTY_AGGREGATION_ALGORITHM, "1"));
+ return version < PROPERTY_AGGREGATION_ALGORITHM_VERSION;
+ }
+
+ protected void upgradeAggregationAlgorithm() {
+ // This upgrade will affect very few contacts, so it can be performed on the
+ // main thread during the initial boot after an OTA
+
+ Log.i(TAG, "Upgrading aggregation algorithm");
+ int count = 0;
+ long start = SystemClock.currentThreadTimeMillis();
+ try {
+ mDb.beginTransaction();
+ Cursor cursor = mDb.query(true,
+ Tables.RAW_CONTACTS + " r1 JOIN " + Tables.RAW_CONTACTS + " r2",
+ new String[]{"r1." + RawContacts._ID},
+ "r1." + RawContacts._ID + "!=r2." + RawContacts._ID +
+ " AND r1." + RawContacts.CONTACT_ID + "=r2." + RawContacts.CONTACT_ID +
+ " AND r1." + RawContacts.ACCOUNT_NAME + "=r2." + RawContacts.ACCOUNT_NAME +
+ " AND r1." + RawContacts.ACCOUNT_TYPE + "=r2." + RawContacts.ACCOUNT_TYPE,
+ null, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ long rawContactId = cursor.getLong(0);
+ mContactAggregator.markForAggregation(rawContactId,
+ RawContacts.AGGREGATION_MODE_DEFAULT, true);
+ count++;
+ }
+ } finally {
+ cursor.close();
+ }
+ mContactAggregator.aggregateInTransaction(mDb);
+ mDb.setTransactionSuccessful();
+ mDbHelper.setProperty(PROPERTY_AGGREGATION_ALGORITHM,
+ String.valueOf(PROPERTY_AGGREGATION_ALGORITHM_VERSION));
+ } finally {
+ mDb.endTransaction();
+ long end = SystemClock.currentThreadTimeMillis();
+ Log.i(TAG, "Aggregation algorithm upgraded for " + count
+ + " contacts, in " + (end - start) + "ms");
+ }
+ }
}
diff --git a/tests/src/com/android/providers/contacts/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/ContactAggregatorTest.java
index 6113856b..66675648 100644
--- a/tests/src/com/android/providers/contacts/ContactAggregatorTest.java
+++ b/tests/src/com/android/providers/contacts/ContactAggregatorTest.java
@@ -46,6 +46,10 @@ import android.test.suitebuilder.annotation.LargeTest;
@LargeTest
public class ContactAggregatorTest 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,
@@ -115,41 +119,47 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
assertEquals("Johna Smitha", displayName);
}
+ public void testNonAggregationFromSameAccount() {
+ long rawContactId1 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ long rawContactId2 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ assertNotAggregated(rawContactId1, rawContactId2);
+ }
+
public void testAggregationOfExactFullNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johnb", "Smithb");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johnb", "Smithb");
assertAggregated(rawContactId1, rawContactId2, "Johnb Smithb");
}
public void testAggregationOfCaseInsensitiveFullNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johnc", "Smithc");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johnc", "smithc");
assertAggregated(rawContactId1, rawContactId2, "Johnc Smithc");
}
public void testAggregationOfLastNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, null, "Johnd");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, null, "johnd");
assertAggregated(rawContactId1, rawContactId2, "Johnd");
}
public void testNonAggregationOfFirstNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johne", "Smithe");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johne", null);
assertNotAggregated(rawContactId1, rawContactId2);
@@ -157,30 +167,30 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
// TODO: should this be allowed to match?
public void testNonAggregationOfLastNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johnf", "Smithf");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, null, "Smithf");
assertNotAggregated(rawContactId1, rawContactId2);
}
public void testAggregationOfConcatenatedFullNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johng", "Smithg");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "johngsmithg", null);
assertAggregated(rawContactId1, rawContactId2, "Johng Smithg");
}
public void testAggregationOfNormalizedFullNameMatch() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "H\u00e9l\u00e8ne", "Bj\u00f8rn");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "helene bjorn", null);
assertAggregated(rawContactId1, rawContactId2, "H\u00e9l\u00e8ne Bj\u00f8rn");
@@ -197,17 +207,17 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationOfNumericNames() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "123", null);
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "1-2-3", null);
assertAggregated(rawContactId1, rawContactId2, "1-2-3");
}
public void testAggregationOfInconsistentlyParsedNames() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
ContentValues values = new ContentValues();
values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave");
@@ -216,7 +226,7 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
values.put(StructuredName.FAMILY_NAME, "Ave");
insertStructuredName(rawContactId1, values);
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
values.clear();
values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave");
values.put(StructuredName.GIVEN_NAME, "604");
@@ -228,14 +238,14 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
public void testAggregationBasedOnMiddleName() {
ContentValues values = new ContentValues();
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
values.put(StructuredName.GIVEN_NAME, "John");
values.put(StructuredName.GIVEN_NAME, "Abigale");
values.put(StructuredName.FAMILY_NAME, "James");
insertStructuredName(rawContactId1, values);
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
values.clear();
values.put(StructuredName.GIVEN_NAME, "John");
values.put(StructuredName.GIVEN_NAME, "Marie");
@@ -246,20 +256,35 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationBasedOnPhoneNumberNoNameData() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertPhoneNumber(rawContactId1, "(888)555-1231");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertPhoneNumber(rawContactId2, "1(888)555-1231");
assertAggregated(rawContactId1, rawContactId2);
}
+ public void testNonAggregationBasedOnPhoneNumberNoNameDataSameAccount() {
+ long rawContactId1 = createRawContact(ACCOUNT_1);
+ insertPhoneNumber(rawContactId1, "(888)555-1231");
+
+ long rawContactId2 = createRawContact(ACCOUNT_2);
+ insertPhoneNumber(rawContactId2, "1(888)555-1231");
+
+ long rawContactId3 = createRawContact(ACCOUNT_1);
+ insertPhoneNumber(rawContactId3, "888-555-1231");
+
+ assertNotAggregated(rawContactId1, rawContactId2);
+ assertNotAggregated(rawContactId1, rawContactId3);
+ assertNotAggregated(rawContactId2, rawContactId3);
+ }
+
public void testAggregationBasedOnPhoneNumberWhenTargetAggregateHasNoName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertPhoneNumber(rawContactId1, "(888)555-1232");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johnl", "Smithl");
insertPhoneNumber(rawContactId2, "1(888)555-1232");
@@ -267,22 +292,22 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationBasedOnPhoneNumberWhenNewContactHasNoName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johnm", "Smithm");
insertPhoneNumber(rawContactId1, "(888)555-1233");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertPhoneNumber(rawContactId2, "1(888)555-1233");
assertAggregated(rawContactId1, rawContactId2);
}
public void testAggregationBasedOnPhoneNumberWithDifferentNames() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Baby", "Bear");
insertPhoneNumber(rawContactId1, "(888)555-1235");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Blind", "Mouse");
insertPhoneNumber(rawContactId2, "1(888)555-1235");
@@ -290,11 +315,11 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationBasedOnPhoneNumberWithJustFirstName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Chick", "Notnull");
insertPhoneNumber(rawContactId1, "(888)555-1236");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Chick", null);
insertPhoneNumber(rawContactId2, "1(888)555-1236");
@@ -302,20 +327,20 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationBasedOnEmailNoNameData() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertEmail(rawContactId1, "lightning@android.com");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertEmail(rawContactId2, "lightning@android.com");
assertAggregated(rawContactId1, rawContactId2);
}
public void testAggregationBasedOnEmailWhenTargetAggregateHasNoName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertEmail(rawContactId1, "mcqueen@android.com");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Lightning", "McQueen");
insertEmail(rawContactId2, "mcqueen@android.com");
@@ -323,22 +348,22 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationBasedOnEmailWhenNewContactHasNoName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Doc", "Hudson");
insertEmail(rawContactId1, "doc@android.com");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertEmail(rawContactId2, "doc@android.com");
assertAggregated(rawContactId1, rawContactId2);
}
public void testAggregationBasedOnEmailWithDifferentNames() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Chick", "Hicks");
insertEmail(rawContactId1, "hicky@android.com");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Luigi", "Guido");
insertEmail(rawContactId2, "hicky@android.com");
@@ -346,41 +371,41 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationByCommonNicknameWithLastName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Bill", "Gore");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "William", "Gore");
assertAggregated(rawContactId1, rawContactId2, "William Gore");
}
public void testAggregationByCommonNicknameOnly() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Lawrence", null);
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Larry", null);
assertAggregated(rawContactId1, rawContactId2, "Lawrence");
}
public void testAggregationByNicknameNoStructuredName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertNickname(rawContactId1, "Frozone");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertNickname(rawContactId2, "Frozone");
assertAggregated(rawContactId1, rawContactId2);
}
public void testAggregationByNicknameWithDifferentNames() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Helen", "Parr");
insertNickname(rawContactId1, "Elastigirl");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Shawn", "Johnson");
insertNickname(rawContactId2, "Elastigirl");
@@ -390,11 +415,11 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
public void testNonAggregationOnOrganization() {
ContentValues values = new ContentValues();
values.put(Organization.TITLE, "Monsters, Inc");
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertOrganization(rawContactId1, values);
insertNickname(rawContactId1, "Boo");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertOrganization(rawContactId2, values);
insertNickname(rawContactId2, "Rendall"); // To force reaggregation
@@ -402,10 +427,10 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationExceptionKeepIn() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johnk", "Smithk");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johnkx", "Smithkx");
long contactId1 = queryContactId(rawContactId1);
@@ -430,10 +455,10 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationExceptionKeepOut() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johnh", "Smithh");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johnh", "Smithh");
setAggregationException(AggregationExceptions.TYPE_KEEP_SEPARATE,
@@ -443,13 +468,13 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testAggregationExceptionKeepOutCheckUpdatesDisplayName() {
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
insertStructuredName(rawContactId1, "Johni", "Smithi");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
insertStructuredName(rawContactId2, "Johnj", "Smithj");
- long rawContactId3 = createRawContact();
+ long rawContactId3 = createRawContact(ACCOUNT_3);
insertStructuredName(rawContactId3, "Johnm", "Smithm");
setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER,
@@ -491,6 +516,51 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
assertEquals("Johnm Smithm", displayName4);
}
+ public void testNonAggregationWithMultipleAffinities() {
+ long rawContactId1 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ long rawContactId2 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ assertNotAggregated(rawContactId1, rawContactId2);
+
+ // There are two aggregates this raw contact could join, so it should join neither
+ long rawContactId3 = createRawContactWithName("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 testSplitBecauseOfMultipleAffinities() {
+ long rawContactId1 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ long rawContactId2 = createRawContactWithName("John", "Doe", ACCOUNT_2);
+ assertAggregated(rawContactId1, rawContactId2);
+
+ // The aggregate this raw contact could join has a raw contact from the same account,
+ // let's not aggregate and break up the existing aggregate because of the ambiguity
+ long rawContactId3 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ assertNotAggregated(rawContactId1, rawContactId3);
+ assertNotAggregated(rawContactId2, rawContactId3);
+ assertNotAggregated(rawContactId1, rawContactId2);
+ }
+
+ public void testNonSplitBecauseOfMultipleAffinitiesWhenOverridden() {
+ long rawContactId1 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ long rawContactId2 = createRawContactWithName("John", "Doe", ACCOUNT_2);
+ long rawContactId3 = createRawContactWithName("John", "Doe", ACCOUNT_3);
+ 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 not aggregate and break up the existing aggregate because of the ambiguity
+ long rawContactId4 = createRawContactWithName("John", "Doe", ACCOUNT_1);
+ assertAggregated(rawContactId1, rawContactId2); // Aggregation exception
+ assertNotAggregated(rawContactId1, rawContactId3);
+ assertNotAggregated(rawContactId1, rawContactId4);
+ assertNotAggregated(rawContactId3, rawContactId4);
+ }
+
public void testAggregationSuggestionsBasedOnName() {
long rawContactId1 = createRawContact();
insertStructuredName(rawContactId1, "Duane", null);
@@ -707,10 +777,10 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
}
public void testVerifiedName() {
- long rawContactId1 = createRawContactWithName("test1", "TEST1");
+ long rawContactId1 = createRawContactWithName("test1", "TEST1", ACCOUNT_1);
storeValue(RawContacts.CONTENT_URI, rawContactId1, RawContacts.NAME_VERIFIED, "1");
- long rawContactId2 = createRawContactWithName("test2", "TEST2");
- long rawContactId3 = createRawContactWithName("test3", "TEST3 LONG");
+ long rawContactId2 = createRawContactWithName("test2", "TEST2", ACCOUNT_2);
+ long rawContactId3 = createRawContactWithName("test3", "TEST3 LONG", ACCOUNT_3);
setAggregationException(AggregationExceptions.TYPE_KEEP_TOGETHER, rawContactId1,
rawContactId2);
@@ -736,12 +806,12 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
public void testAggregationModeSuspendedSeparateTransactions() {
// Setting aggregation mode to SUSPENDED should prevent aggregation from happening
- long rawContactId1 = createRawContact();
+ long rawContactId1 = createRawContact(ACCOUNT_1);
storeValue(RawContacts.CONTENT_URI, rawContactId1,
RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
Uri name1 = insertStructuredName(rawContactId1, "THE", "SAME");
- long rawContactId2 = createRawContact();
+ long rawContactId2 = createRawContact(ACCOUNT_2);
storeValue(RawContacts.CONTENT_URI, rawContactId2,
RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_SUSPENDED);
insertStructuredName(rawContactId2, "THE", "SAME");
@@ -846,7 +916,8 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
public void testAggregationModeSuspendedOverriddenByAggException() throws Exception {
ContentProviderOperation cpo1 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
- .withValues(new ContentValues())
+ .withValue(RawContacts.ACCOUNT_NAME, "a")
+ .withValue(RawContacts.ACCOUNT_TYPE, "b")
.build();
ContentProviderOperation cpo2 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 0)
@@ -855,7 +926,8 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test {
.withValue(StructuredName.FAMILY_NAME, "Doe")
.build();
ContentProviderOperation cpo3 = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
- .withValues(new ContentValues())
+ .withValue(RawContacts.ACCOUNT_NAME, "c")
+ .withValue(RawContacts.ACCOUNT_TYPE, "d")
.build();
ContentProviderOperation cpo4 = ContentProviderOperation.newInsert(Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, 2)