aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
3 files changed, 239 insertions, 31 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");
+ }
+ }
}