summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java29
-rw-r--r--src/com/android/providers/contacts/DataRowHandler.java7
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java6
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java7
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForEmail.java5
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java5
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForIdentity.java7
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForIm.java7
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForNickname.java7
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForNote.java7
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForOrganization.java5
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForPhoto.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForStructuredName.java4
-rw-r--r--src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java4
-rw-r--r--src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java1067
-rw-r--r--src/com/android/providers/contacts/aggregation/ContactAggregator.java1862
-rw-r--r--src/com/android/providers/contacts/aggregation/ContactAggregator2.java1885
-rw-r--r--src/com/android/providers/contacts/aggregation/util/ContactMatcher.java114
-rw-r--r--src/com/android/providers/contacts/aggregation/util/MatchScore.java149
-rw-r--r--src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java99
-rw-r--r--src/com/android/providers/contacts/aggregation/util/RawContactMatchingCandidates.java8
22 files changed, 442 insertions, 4850 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index afbec70..2fc1d82 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -105,12 +105,12 @@ import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.StreamItemPhotos;
import android.provider.ContactsContract.StreamItems;
import android.provider.OpenableColumns;
+import android.provider.Settings.Global;
import android.provider.SyncStateContract;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-
import com.android.common.content.ProjectionMap;
import com.android.common.content.SyncStateContentProviderHelper;
import com.android.common.io.MoreCloseables;
@@ -144,8 +144,10 @@ import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.ContactsDatabaseHelper.ViewGroupsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Views;
import com.android.providers.contacts.SearchIndexManager.FtsQueryBuilder;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator.AggregationSuggestionParameter;
import com.android.providers.contacts.aggregation.ContactAggregator;
-import com.android.providers.contacts.aggregation.ContactAggregator.AggregationSuggestionParameter;
+import com.android.providers.contacts.aggregation.ContactAggregator2;
import com.android.providers.contacts.aggregation.ProfileAggregator;
import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
import com.android.providers.contacts.database.ContactsTableUtil;
@@ -162,7 +164,6 @@ import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
-
import libcore.io.IoUtils;
import java.io.BufferedWriter;
@@ -1344,7 +1345,8 @@ public class ContactsProvider2 extends AbstractContactsProvider
// Depending on whether the action being performed is for the profile or not, we will use one of
// two aggregator instances.
- private final ThreadLocal<ContactAggregator> mAggregator = new ThreadLocal<ContactAggregator>();
+ private final ThreadLocal<AbstractContactAggregator> mAggregator =
+ new ThreadLocal<AbstractContactAggregator>();
// Depending on whether the action being performed is for the profile or not, we will use one of
// two photo store instances (with their files stored in separate sub-directories).
@@ -1405,7 +1407,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private Account mAccount;
- private ContactAggregator mContactAggregator;
+ private AbstractContactAggregator mContactAggregator;
private ContactAggregator mProfileAggregator;
// Duration in milliseconds that pre-authorized URIs will remain valid.
@@ -1573,8 +1575,15 @@ public class ContactsProvider2 extends AbstractContactsProvider
mPostalSplitter = new PostalSplitter(mCurrentLocales.getPrimaryLocale());
mCommonNicknameCache = new CommonNicknameCache(mContactsHelper.getReadableDatabase());
ContactLocaleUtils.setLocales(mCurrentLocales);
- mContactAggregator = new ContactAggregator(this, mContactsHelper,
- createPhotoPriorityResolver(context), mNameSplitter, mCommonNicknameCache);
+
+ int value = android.provider.Settings.Global.getInt(context.getContentResolver(),
+ Global.NEW_CONTACT_AGGREGATOR, 0);
+ mContactAggregator = (value == 0)
+ ? new ContactAggregator(this, mContactsHelper,
+ createPhotoPriorityResolver(context), mNameSplitter, mCommonNicknameCache)
+ : new ContactAggregator2(this, mContactsHelper,
+ createPhotoPriorityResolver(context), mNameSplitter, mCommonNicknameCache);
+
mContactAggregator.setEnabled(SystemProperties.getBoolean(AGGREGATE_CONTACTS, true));
mProfileAggregator = new ProfileAggregator(this, mProfileHelper,
createPhotoPriorityResolver(context), mNameSplitter, mCommonNicknameCache);
@@ -1596,7 +1605,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
}
private void initDataRowHandlers(Map<String, DataRowHandler> handlerMap,
- ContactsDatabaseHelper dbHelper, ContactAggregator contactAggregator,
+ ContactsDatabaseHelper dbHelper, AbstractContactAggregator contactAggregator,
PhotoStore photoStore) {
Context context = getContext();
handlerMap.put(Email.CONTENT_ITEM_TYPE,
@@ -4391,7 +4400,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
int count = db.update(Tables.RAW_CONTACTS, values, selection, mSelectionArgs1);
if (count != 0) {
- final ContactAggregator aggregator = mAggregator.get();
+ final AbstractContactAggregator aggregator = mAggregator.get();
int aggregationMode = getIntValue(
values, RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT);
@@ -4646,7 +4655,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
db.replace(Tables.AGGREGATION_EXCEPTIONS, AggregationExceptions._ID, exceptionValues);
}
- final ContactAggregator aggregator = mAggregator.get();
+ final AbstractContactAggregator aggregator = mAggregator.get();
aggregator.invalidateAggregationExceptionCache();
aggregator.markForAggregation(rawContactId1, RawContacts.AGGREGATION_MODE_DEFAULT, true);
aggregator.markForAggregation(rawContactId2, RawContacts.AGGREGATION_MODE_DEFAULT, true);
diff --git a/src/com/android/providers/contacts/DataRowHandler.java b/src/com/android/providers/contacts/DataRowHandler.java
index 8e3c20f..dbe8cbc 100644
--- a/src/com/android/providers/contacts/DataRowHandler.java
+++ b/src/com/android/providers/contacts/DataRowHandler.java
@@ -26,12 +26,11 @@ import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Data;
import android.text.TextUtils;
-
import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handles inserts and update for a specific Data type.
@@ -74,14 +73,14 @@ public abstract class DataRowHandler {
protected final Context mContext;
protected final ContactsDatabaseHelper mDbHelper;
- protected final ContactAggregator mContactAggregator;
+ protected final AbstractContactAggregator mContactAggregator;
protected String[] mSelectionArgs1 = new String[1];
protected final String mMimetype;
protected long mMimetypeId;
@SuppressWarnings("all")
public DataRowHandler(Context context, ContactsDatabaseHelper dbHelper,
- ContactAggregator aggregator, String mimetype) {
+ AbstractContactAggregator aggregator, String mimetype) {
mContext = context;
mDbHelper = dbHelper;
mContactAggregator = aggregator;
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
index 0bb17c2..063fcdb 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCommonDataKind.java
@@ -21,8 +21,7 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.BaseTypes;
import android.text.TextUtils;
-
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Superclass for data row handlers that deal with types (e.g. Home, Work, Other) and
@@ -34,7 +33,8 @@ public class DataRowHandlerForCommonDataKind extends DataRowHandler {
private final String mLabelColumn;
public DataRowHandlerForCommonDataKind(Context context, ContactsDatabaseHelper dbHelper,
- ContactAggregator aggregator, String mimetype, String typeColumn, String labelColumn) {
+ AbstractContactAggregator aggregator, String mimetype, String typeColumn,
+ String labelColumn) {
super(context, dbHelper, aggregator, mimetype);
mTypeColumn = typeColumn;
mLabelColumn = labelColumn;
diff --git a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
index 502b835..1de0823 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForCustomMimetype.java
@@ -16,13 +16,12 @@
package com.android.providers.contacts;
import android.content.Context;
-
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
public class DataRowHandlerForCustomMimetype extends DataRowHandler {
- public DataRowHandlerForCustomMimetype(Context context,
- ContactsDatabaseHelper dbHelper, ContactAggregator aggregator, String mimetype) {
+ public DataRowHandlerForCustomMimetype(Context context, ContactsDatabaseHelper dbHelper,
+ AbstractContactAggregator aggregator, String mimetype) {
super(context, dbHelper, aggregator, mimetype);
}
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForEmail.java b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
index 38cb2e1..539c959 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForEmail.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForEmail.java
@@ -20,9 +20,8 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.Email;
-
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for email address data rows.
@@ -30,7 +29,7 @@ import com.android.providers.contacts.aggregation.ContactAggregator;
public class DataRowHandlerForEmail extends DataRowHandlerForCommonDataKind {
public DataRowHandlerForEmail(
- Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) {
+ Context context, ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator, Email.CONTENT_ITEM_TYPE, Email.TYPE, Email.LABEL);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
index 0d2427a..e291986 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForGroupMembership.java
@@ -23,7 +23,6 @@ import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
-
import com.android.providers.contacts.ContactsDatabaseHelper.Clauses;
import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns;
@@ -31,7 +30,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.Projections;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.ContactsProvider2.GroupIdCacheEntry;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
import java.util.ArrayList;
import java.util.HashMap;
@@ -66,7 +65,7 @@ public class DataRowHandlerForGroupMembership extends DataRowHandler {
private final HashMap<String, ArrayList<GroupIdCacheEntry>> mGroupIdCache;
public DataRowHandlerForGroupMembership(Context context, ContactsDatabaseHelper dbHelper,
- ContactAggregator aggregator,
+ AbstractContactAggregator aggregator,
HashMap<String, ArrayList<GroupIdCacheEntry>> groupIdCache) {
super(context, dbHelper, aggregator, GroupMembership.CONTENT_ITEM_TYPE);
mGroupIdCache = groupIdCache;
diff --git a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
index 48ce5e4..32e9757 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForIdentity.java
@@ -20,15 +20,14 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.Identity;
-
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for Identity data rows.
*/
public class DataRowHandlerForIdentity extends DataRowHandler {
- public DataRowHandlerForIdentity(
- Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) {
+ public DataRowHandlerForIdentity(Context context, ContactsDatabaseHelper dbHelper,
+ AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator, Identity.CONTENT_ITEM_TYPE);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForIm.java b/src/com/android/providers/contacts/DataRowHandlerForIm.java
index faf10ad..9a2a56e 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForIm.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForIm.java
@@ -18,17 +18,16 @@ package com.android.providers.contacts;
import android.content.ContentValues;
import android.content.Context;
import android.provider.ContactsContract.CommonDataKinds.Im;
-
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for IM address data rows.
*/
public class DataRowHandlerForIm extends DataRowHandlerForCommonDataKind {
- public DataRowHandlerForIm(
- Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) {
+ public DataRowHandlerForIm(Context context, ContactsDatabaseHelper dbHelper,
+ AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator, Im.CONTENT_ITEM_TYPE, Im.TYPE, Im.LABEL);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNickname.java b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
index 9c32df9..03b96a3 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNickname.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNickname.java
@@ -21,17 +21,16 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.text.TextUtils;
-
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for nickname data rows.
*/
public class DataRowHandlerForNickname extends DataRowHandlerForCommonDataKind {
- public DataRowHandlerForNickname(
- Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) {
+ public DataRowHandlerForNickname(Context context, ContactsDatabaseHelper dbHelper,
+ AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator, Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE,
Nickname.LABEL);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForNote.java b/src/com/android/providers/contacts/DataRowHandlerForNote.java
index ea73637..fc602f1 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForNote.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForNote.java
@@ -18,17 +18,16 @@ package com.android.providers.contacts;
import android.content.ContentValues;
import android.content.Context;
import android.provider.ContactsContract.CommonDataKinds.Note;
-
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for note data rows.
*/
public class DataRowHandlerForNote extends DataRowHandler {
- public DataRowHandlerForNote(
- Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) {
+ public DataRowHandlerForNote(Context context, ContactsDatabaseHelper dbHelper,
+ AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator, Note.CONTENT_ITEM_TYPE);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
index 629d949..66a3b1b 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForOrganization.java
@@ -22,10 +22,9 @@ import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.Data;
-
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for organization data rows.
@@ -33,7 +32,7 @@ import com.android.providers.contacts.aggregation.ContactAggregator;
public class DataRowHandlerForOrganization extends DataRowHandlerForCommonDataKind {
public DataRowHandlerForOrganization(Context context, ContactsDatabaseHelper dbHelper,
- ContactAggregator aggregator) {
+ AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator,
Organization.CONTENT_ITEM_TYPE, Organization.TYPE, Organization.LABEL);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
index 16faf2a..052252e 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoneNumber.java
@@ -26,7 +26,7 @@ import android.text.TextUtils;
import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for phone number data rows.
@@ -34,7 +34,7 @@ import com.android.providers.contacts.aggregation.ContactAggregator;
public class DataRowHandlerForPhoneNumber extends DataRowHandlerForCommonDataKind {
public DataRowHandlerForPhoneNumber(Context context,
- ContactsDatabaseHelper dbHelper, ContactAggregator aggregator) {
+ ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator) {
super(context, dbHelper, aggregator, Phone.CONTENT_ITEM_TYPE, Phone.TYPE, Phone.LABEL);
}
diff --git a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
index bfaa501..532a852 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForPhoto.java
@@ -22,7 +22,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.util.Log;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
import java.io.IOException;
@@ -46,7 +46,7 @@ public class DataRowHandlerForPhoto extends DataRowHandler {
/* package */ static final String SKIP_PROCESSING_KEY = "skip_processing";
public DataRowHandlerForPhoto(
- Context context, ContactsDatabaseHelper dbHelper, ContactAggregator aggregator,
+ Context context, ContactsDatabaseHelper dbHelper, AbstractContactAggregator aggregator,
PhotoStore photoStore, int maxDisplayPhotoDim, int maxThumbnailPhotoDim) {
super(context, dbHelper, aggregator, Photo.CONTENT_ITEM_TYPE);
mPhotoStore = photoStore;
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
index ba6777d..044e972 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredName.java
@@ -25,7 +25,7 @@ import android.provider.ContactsContract.PhoneticNameStyle;
import android.text.TextUtils;
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for email address data rows.
@@ -36,7 +36,7 @@ public class DataRowHandlerForStructuredName extends DataRowHandler {
private final StringBuilder mSb = new StringBuilder();
public DataRowHandlerForStructuredName(Context context, ContactsDatabaseHelper dbHelper,
- ContactAggregator aggregator, NameSplitter splitter,
+ AbstractContactAggregator aggregator, NameSplitter splitter,
NameLookupBuilder nameLookupBuilder) {
super(context, dbHelper, aggregator, StructuredName.CONTENT_ITEM_TYPE);
mSplitter = splitter;
diff --git a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
index 26483ed..7fc97b7 100644
--- a/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
+++ b/src/com/android/providers/contacts/DataRowHandlerForStructuredPostal.java
@@ -23,7 +23,7 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.text.TextUtils;
import com.android.providers.contacts.SearchIndexManager.IndexBuilder;
-import com.android.providers.contacts.aggregation.ContactAggregator;
+import com.android.providers.contacts.aggregation.AbstractContactAggregator;
/**
* Handler for postal address data rows.
@@ -46,7 +46,7 @@ public class DataRowHandlerForStructuredPostal extends DataRowHandler {
private final PostalSplitter mSplitter;
public DataRowHandlerForStructuredPostal(Context context, ContactsDatabaseHelper dbHelper,
- ContactAggregator aggregator, PostalSplitter splitter) {
+ AbstractContactAggregator aggregator, PostalSplitter splitter) {
super(context, dbHelper, aggregator, StructuredPostal.CONTENT_ITEM_TYPE);
mSplitter = splitter;
}
diff --git a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
index 2eeea25..6948f23 100644
--- a/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/AbstractContactAggregator.java
@@ -23,12 +23,12 @@ import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Identity;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.FullNameStyle;
@@ -39,7 +39,6 @@ import android.provider.ContactsContract.StatusUpdates;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.providers.contacts.ContactLookupKey;
import com.android.providers.contacts.ContactsDatabaseHelper;
@@ -65,14 +64,11 @@ import com.android.providers.contacts.TransactionContext;
import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
import com.android.providers.contacts.aggregation.util.ContactAggregatorHelper;
import com.android.providers.contacts.aggregation.util.ContactMatcher;
-import com.android.providers.contacts.aggregation.util.ContactMatcher.MatchScore;
-import com.android.providers.contacts.database.ContactsTableUtil;
+import com.android.providers.contacts.aggregation.util.MatchScore;
import com.android.providers.contacts.util.Clock;
-
import com.google.android.collect.Maps;
-import com.google.android.collect.Sets;
-import com.google.common.collect.Multimap;
import com.google.common.collect.HashMultimap;
+import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collections;
@@ -84,18 +80,16 @@ import java.util.Locale;
import java.util.Set;
/**
- * ContactAggregator deals with aggregating contact information coming from different sources.
- * Two John Doe contacts from two disjoint sources are presumed to be the same
- * person unless the user declares otherwise.
+ * Base class of contact aggregator and profile aggregator
*/
-public class AbstractContactAggregator {
+public abstract class AbstractContactAggregator {
- private static final String TAG = "ContactAggregator";
+ protected static final String TAG = "ContactAggregator";
- private static final boolean DEBUG_LOGGING = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
+ protected static final boolean DEBUG_LOGGING = Log.isLoggable(TAG, Log.DEBUG);
+ protected static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
- private static final String STRUCTURED_NAME_BASED_LOOKUP_SQL =
+ protected static final String STRUCTURED_NAME_BASED_LOOKUP_SQL =
NameLookupColumns.NAME_TYPE + " IN ("
+ NameLookupType.NAME_EXACT + ","
+ NameLookupType.NAME_VARIANT + ","
@@ -106,7 +100,7 @@ public class AbstractContactAggregator {
* SQL statement that sets the {@link ContactsColumns#LAST_STATUS_UPDATE_ID} column
* on the contact to point to the latest social status update.
*/
- private static final String UPDATE_LAST_STATUS_UPDATE_ID_SQL =
+ protected static final String UPDATE_LAST_STATUS_UPDATE_ID_SQL =
"UPDATE " + Tables.CONTACTS +
" SET " + ContactsColumns.LAST_STATUS_UPDATE_ID + "=" +
"(SELECT " + DataColumns.CONCRETE_ID +
@@ -129,13 +123,13 @@ public class AbstractContactAggregator {
public static final int LOG_SYNC_CONTACTS_AGGREGATION = 2747;
// If we encounter more than this many contacts with matching names, aggregate only this many
- private static final int PRIMARY_HIT_LIMIT = 15;
- private static final String PRIMARY_HIT_LIMIT_STRING = String.valueOf(PRIMARY_HIT_LIMIT);
+ protected static final int PRIMARY_HIT_LIMIT = 15;
+ protected static final String PRIMARY_HIT_LIMIT_STRING = String.valueOf(PRIMARY_HIT_LIMIT);
// If we encounter more than this many contacts with matching phone number or email,
// don't attempt to aggregate - this is likely an error or a shared corporate data element.
- private static final int SECONDARY_HIT_LIMIT = 20;
- private static final String SECONDARY_HIT_LIMIT_STRING = String.valueOf(SECONDARY_HIT_LIMIT);
+ protected static final int SECONDARY_HIT_LIMIT = 20;
+ protected static final String SECONDARY_HIT_LIMIT_STRING = String.valueOf(SECONDARY_HIT_LIMIT);
// If we encounter no less than this many raw contacts in the best matching contact during
// aggregation, don't attempt to aggregate - this is likely an error or a shared corporate
@@ -145,54 +139,50 @@ public class AbstractContactAggregator {
// If we encounter more than this many contacts with matching name during aggregation
// suggestion lookup, ignore the remaining results.
- private static final int FIRST_LETTER_SUGGESTION_HIT_LIMIT = 100;
-
- // Return code for the canJoinIntoContact method.
- private static final int JOIN = 1;
- private static final int KEEP_SEPARATE = 0;
- private static final int RE_AGGREGATE = -1;
-
- private final ContactsProvider2 mContactsProvider;
- private final ContactsDatabaseHelper mDbHelper;
- private PhotoPriorityResolver mPhotoPriorityResolver;
- private final NameSplitter mNameSplitter;
- private final CommonNicknameCache mCommonNicknameCache;
-
- private boolean mEnabled = true;
-
- /** Precompiled sql statement for setting an aggregated presence */
- private SQLiteStatement mAggregatedPresenceReplace;
- private SQLiteStatement mPresenceContactIdUpdate;
- private SQLiteStatement mRawContactCountQuery;
- private SQLiteStatement mAggregatedPresenceDelete;
- private SQLiteStatement mMarkForAggregation;
- private SQLiteStatement mPhotoIdUpdate;
- private SQLiteStatement mDisplayNameUpdate;
- private SQLiteStatement mLookupKeyUpdate;
- private SQLiteStatement mStarredUpdate;
- private SQLiteStatement mPinnedUpdate;
- private SQLiteStatement mContactIdAndMarkAggregatedUpdate;
- private SQLiteStatement mContactIdUpdate;
- private SQLiteStatement mMarkAggregatedUpdate;
- private SQLiteStatement mContactUpdate;
- private SQLiteStatement mContactInsert;
- private SQLiteStatement mResetPinnedForRawContact;
-
- private HashMap<Long, Integer> mRawContactsMarkedForAggregation = Maps.newHashMap();
-
- private String[] mSelectionArgs1 = new String[1];
- private String[] mSelectionArgs2 = new String[2];
-
- private long mMimeTypeIdIdentity;
- private long mMimeTypeIdEmail;
- private long mMimeTypeIdPhoto;
- private long mMimeTypeIdPhone;
- private String mRawContactsQueryByRawContactId;
- private String mRawContactsQueryByContactId;
- private StringBuilder mSb = new StringBuilder();
- private MatchCandidateList mCandidates = new MatchCandidateList();
- private ContactMatcher mMatcher = new ContactMatcher();
- private DisplayNameCandidate mDisplayNameCandidate = new DisplayNameCandidate();
+ protected static final int FIRST_LETTER_SUGGESTION_HIT_LIMIT = 100;
+
+ protected final ContactsProvider2 mContactsProvider;
+ protected final ContactsDatabaseHelper mDbHelper;
+ protected PhotoPriorityResolver mPhotoPriorityResolver;
+ protected final NameSplitter mNameSplitter;
+ protected final CommonNicknameCache mCommonNicknameCache;
+
+ protected boolean mEnabled = true;
+
+ /**
+ * Precompiled sql statement for setting an aggregated presence
+ */
+ protected SQLiteStatement mRawContactCountQuery;
+ protected SQLiteStatement mAggregatedPresenceDelete;
+ protected SQLiteStatement mAggregatedPresenceReplace;
+ protected SQLiteStatement mPresenceContactIdUpdate;
+ protected SQLiteStatement mMarkForAggregation;
+ protected SQLiteStatement mPhotoIdUpdate;
+ protected SQLiteStatement mDisplayNameUpdate;
+ protected SQLiteStatement mLookupKeyUpdate;
+ protected SQLiteStatement mStarredUpdate;
+ protected SQLiteStatement mPinnedUpdate;
+ protected SQLiteStatement mContactIdAndMarkAggregatedUpdate;
+ protected SQLiteStatement mContactIdUpdate;
+ protected SQLiteStatement mMarkAggregatedUpdate;
+ protected SQLiteStatement mContactUpdate;
+ protected SQLiteStatement mContactInsert;
+ protected SQLiteStatement mResetPinnedForRawContact;
+
+ protected HashMap<Long, Integer> mRawContactsMarkedForAggregation = Maps.newHashMap();
+
+ protected String[] mSelectionArgs1 = new String[1];
+ protected String[] mSelectionArgs2 = new String[2];
+
+ protected long mMimeTypeIdIdentity;
+ protected long mMimeTypeIdEmail;
+ protected long mMimeTypeIdPhoto;
+ protected long mMimeTypeIdPhone;
+ protected String mRawContactsQueryByRawContactId;
+ protected String mRawContactsQueryByContactId;
+ protected StringBuilder mSb = new StringBuilder();
+ protected MatchCandidateList mCandidates = new MatchCandidateList();
+ protected DisplayNameCandidate mDisplayNameCandidate = new DisplayNameCandidate();
/**
* Parameter for the suggestion lookup query.
@@ -212,7 +202,7 @@ public class AbstractContactAggregator {
* constructs a bunch of NameMatchCandidate objects for various potential matches
* and then executes the search in bulk.
*/
- private static class NameMatchCandidate {
+ protected static class NameMatchCandidate {
String mName;
int mLookupType;
@@ -226,9 +216,9 @@ public class AbstractContactAggregator {
* A list of {@link NameMatchCandidate} that keeps its elements even when the list is
* truncated. This is done for optimization purposes to avoid excessive object allocation.
*/
- private static class MatchCandidateList {
- private final ArrayList<NameMatchCandidate> mList = new ArrayList<NameMatchCandidate>();
- private int mCount;
+ protected static class MatchCandidateList {
+ protected final ArrayList<NameMatchCandidate> mList = new ArrayList<NameMatchCandidate>();
+ protected int mCount;
/**
* Adds a {@link NameMatchCandidate} element or updates the next one if it already exists.
@@ -412,7 +402,7 @@ public class AbstractContactAggregator {
return mEnabled;
}
- private interface AggregationQuery {
+ protected interface AggregationQuery {
String SQL =
"SELECT " + RawContacts._ID + "," + RawContacts.CONTACT_ID +
", " + RawContactsColumns.ACCOUNT_ID +
@@ -488,7 +478,7 @@ public class AbstractContactAggregator {
for (int i = 0; i < actualCount; i++) {
aggregateContact(txContext, db, rawContactIds[i], accountIds[i], contactIds[i],
- mCandidates, mMatcher);
+ mCandidates);
}
long elapsedTime = System.currentTimeMillis() - start;
@@ -565,7 +555,7 @@ public class AbstractContactAggregator {
private static class RawContactIdAndAggregationModeQuery {
public static final String TABLE = Tables.RAW_CONTACTS;
- public static final String[] COLUMNS = { RawContacts._ID, RawContacts.AGGREGATION_MODE };
+ public static final String[] COLUMNS = {RawContacts._ID, RawContacts.AGGREGATION_MODE};
public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
@@ -576,7 +566,7 @@ public class AbstractContactAggregator {
/**
* Marks all constituent raw contacts of an aggregated contact for re-aggregation.
*/
- private void markContactForAggregation(SQLiteDatabase db, long contactId) {
+ protected void markContactForAggregation(SQLiteDatabase db, long contactId) {
mSelectionArgs1[0] = String.valueOf(contactId);
Cursor cursor = db.query(RawContactIdAndAggregationModeQuery.TABLE,
RawContactIdAndAggregationModeQuery.COLUMNS,
@@ -676,7 +666,6 @@ public class AbstractContactAggregator {
}
MatchCandidateList candidates = new MatchCandidateList();
- ContactMatcher matcher = new ContactMatcher();
long contactId = 0;
long accountId = 0;
@@ -694,7 +683,7 @@ public class AbstractContactAggregator {
}
aggregateContact(txContext, db, rawContactId, accountId, contactId,
- candidates, matcher);
+ candidates);
}
public void updateAggregateData(TransactionContext txContext, long contactId) {
@@ -711,7 +700,7 @@ public class AbstractContactAggregator {
updateAggregatedStatusUpdate(contactId);
}
- private void updateAggregatedStatusUpdate(long contactId) {
+ protected void updateAggregatedStatusUpdate(long contactId) {
mAggregatedPresenceReplace.bindLong(1, contactId);
mAggregatedPresenceReplace.bindLong(2, contactId);
mAggregatedPresenceReplace.execute();
@@ -731,302 +720,21 @@ public class AbstractContactAggregator {
* Given a specific raw contact, finds all matching aggregate contacts and chooses the one
* with the highest match score. If no such contact is found, creates a new contact.
*/
- private synchronized void aggregateContact(TransactionContext txContext, SQLiteDatabase db,
- long rawContactId, long accountId, long currentContactId, MatchCandidateList candidates,
- ContactMatcher matcher) {
-
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "aggregateContact: rid=" + rawContactId + " cid=" + currentContactId);
- }
-
- int aggregationMode = RawContacts.AGGREGATION_MODE_DEFAULT;
-
- Integer aggModeObject = mRawContactsMarkedForAggregation.remove(rawContactId);
- if (aggModeObject != null) {
- aggregationMode = aggModeObject;
- }
-
- long contactId = -1; // Best matching contact ID.
- boolean needReaggregate = false;
-
- final Set<Long> rawContactIdsInSameAccount = new HashSet<Long>();
- final Set<Long> rawContactIdsInOtherAccount = new HashSet<Long>();
- if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
- candidates.clear();
- matcher.clear();
-
- contactId = pickBestMatchBasedOnExceptions(db, rawContactId, matcher);
- if (contactId == -1) {
-
- // If this is a newly inserted contact or a visible contact, look for
- // data matches.
- if (currentContactId == 0
- || mDbHelper.isContactInDefaultDirectory(db, currentContactId)) {
- contactId = pickBestMatchBasedOnData(db, rawContactId, candidates, matcher);
- }
-
- // If we found an best matched contact, find out if the raw contact can be joined
- // into it
- if (contactId != -1 && contactId != currentContactId) {
- // List all raw contact ID and their account ID mappings in contact
- // [contactId] excluding raw_contact [rawContactId].
-
- // Based on the mapping, create two sets of raw contact IDs in
- // [rawContactAccountId] and not in [rawContactAccountId]. We don't always
- // need them, so lazily initialize them.
- mSelectionArgs2[0] = String.valueOf(contactId);
- mSelectionArgs2[1] = String.valueOf(rawContactId);
- final Cursor rawContactsToAccountsCursor = db.rawQuery(
- "SELECT " + RawContacts._ID + ", " + RawContactsColumns.ACCOUNT_ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=?" +
- " AND " + RawContacts._ID + "!=?",
- mSelectionArgs2);
- try {
- rawContactsToAccountsCursor.moveToPosition(-1);
- while (rawContactsToAccountsCursor.moveToNext()) {
- final long rcId = rawContactsToAccountsCursor.getLong(0);
- final long rc_accountId = rawContactsToAccountsCursor.getLong(1);
- if (rc_accountId == accountId) {
- rawContactIdsInSameAccount.add(rcId);
- } else {
- rawContactIdsInOtherAccount.add(rcId);
- }
- }
- } finally {
- rawContactsToAccountsCursor.close();
- }
- final int actionCode;
- final int totalNumOfRawContactsInCandidate = rawContactIdsInSameAccount.size()
- + rawContactIdsInOtherAccount.size();
- if (totalNumOfRawContactsInCandidate >= AGGREGATION_CONTACT_SIZE_LIMIT) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "Too many raw contacts (" + totalNumOfRawContactsInCandidate
- + ") in the best matching contact, so skip aggregation");
- }
- actionCode = KEEP_SEPARATE;
- } else {
- actionCode = canJoinIntoContact(db, rawContactId,
- rawContactIdsInSameAccount, rawContactIdsInOtherAccount);
- }
- if (actionCode == KEEP_SEPARATE) {
- contactId = -1;
- } else if (actionCode == RE_AGGREGATE) {
- needReaggregate = true;
- }
- }
- }
- } else if (aggregationMode == RawContacts.AGGREGATION_MODE_DISABLED) {
- return;
- }
-
- // # of raw_contacts in the [currentContactId] contact excluding the [rawContactId]
- // raw_contact.
- long currentContactContentsCount = 0;
-
- if (currentContactId != 0) {
- mRawContactCountQuery.bindLong(1, currentContactId);
- mRawContactCountQuery.bindLong(2, rawContactId);
- currentContactContentsCount = mRawContactCountQuery.simpleQueryForLong();
- }
-
- // If there are no other raw contacts in the current aggregate, we might as well reuse it.
- // Also, if the aggregation mode is SUSPENDED, we must reuse the same aggregate.
- if (contactId == -1
- && currentContactId != 0
- && (currentContactContentsCount == 0
- || aggregationMode == RawContacts.AGGREGATION_MODE_SUSPENDED)) {
- contactId = currentContactId;
- }
-
- if (contactId == currentContactId) {
- // Aggregation unchanged
- markAggregated(rawContactId);
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "Aggregation unchanged");
- }
- } else if (contactId == -1) {
- // create new contact for [rawContactId]
- createContactForRawContacts(db, txContext, Sets.newHashSet(rawContactId), null);
- if (currentContactContentsCount > 0) {
- updateAggregateData(txContext, currentContactId);
- }
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "create new contact for rid=" + rawContactId);
- }
- } else if (needReaggregate) {
- // re-aggregate
- final Set<Long> allRawContactIdSet = new HashSet<Long>();
- allRawContactIdSet.addAll(rawContactIdsInSameAccount);
- allRawContactIdSet.addAll(rawContactIdsInOtherAccount);
- // If there is no other raw contacts aggregated with the given raw contact currently,
- // we might as well reuse it.
- currentContactId = (currentContactId != 0 && currentContactContentsCount == 0)
- ? currentContactId : 0;
- reAggregateRawContacts(txContext, db, contactId, currentContactId, rawContactId,
- allRawContactIdSet);
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "Re-aggregating rid=" + rawContactId + " and cid=" + contactId);
- }
- } else {
- // Joining with an existing aggregate
- if (currentContactContentsCount == 0) {
- // Delete a previous aggregate if it only contained this raw contact
- ContactsTableUtil.deleteContact(db, currentContactId);
-
- mAggregatedPresenceDelete.bindLong(1, currentContactId);
- mAggregatedPresenceDelete.execute();
- }
-
- clearSuperPrimarySetting(db, contactId, rawContactId);
- setContactIdAndMarkAggregated(rawContactId, contactId);
- computeAggregateData(db, contactId, mContactUpdate);
- mContactUpdate.bindLong(ContactReplaceSqlStatement.CONTACT_ID, contactId);
- mContactUpdate.execute();
- mDbHelper.updateContactVisible(txContext, contactId);
- updateAggregatedStatusUpdate(contactId);
- // Make sure the raw contact does not contribute to the current contact
- if (currentContactId != 0) {
- updateAggregateData(txContext, currentContactId);
- }
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "Join rid=" + rawContactId + " with cid=" + contactId);
- }
- }
- }
-
- /**
- * Find out which mime-types are shared by raw contact of {@code rawContactId} and raw contacts
- * of {@code contactId}. Clear the is_super_primary settings for these mime-types.
- */
- private void clearSuperPrimarySetting(SQLiteDatabase db, long contactId, long rawContactId) {
- final String[] args = {String.valueOf(contactId), String.valueOf(rawContactId)};
-
- // Find out which mime-types exist with is_super_primary=true on both the raw contact of
- // rawContactId and raw contacts of contactId
- int index = 0;
- final StringBuilder mimeTypeCondition = new StringBuilder();
- mimeTypeCondition.append(" AND " + DataColumns.MIMETYPE_ID + " IN (");
-
- final Cursor c = db.rawQuery(
- "SELECT DISTINCT(a." + DataColumns.MIMETYPE_ID + ")" +
- " FROM (SELECT " + DataColumns.MIMETYPE_ID + " FROM " + Tables.DATA + " WHERE " +
- Data.IS_SUPER_PRIMARY + " =1 AND " +
- Data.RAW_CONTACT_ID + " IN (SELECT " + RawContacts._ID + " FROM " +
- Tables.RAW_CONTACTS + " WHERE " + RawContacts.CONTACT_ID + "=?1)) AS a" +
- " JOIN (SELECT " + DataColumns.MIMETYPE_ID + " FROM " + Tables.DATA + " WHERE " +
- Data.IS_SUPER_PRIMARY + " =1 AND " +
- Data.RAW_CONTACT_ID + "=?2) AS b" +
- " ON a." + DataColumns.MIMETYPE_ID + "=b." + DataColumns.MIMETYPE_ID,
- args);
- try {
- c.moveToPosition(-1);
- while (c.moveToNext()) {
- if (index > 0) {
- mimeTypeCondition.append(',');
- }
- mimeTypeCondition.append(c.getLong((0)));
- index++;
- }
- } finally {
- c.close();
- }
-
- if (index == 0) {
- return;
- }
-
- // Clear is_super_primary setting for all the mime-types with is_super_primary=true
- // 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 " + RawContacts.CONTACT_ID + "=?1)" +
- " OR " + Data.RAW_CONTACT_ID + "=?2)";
-
- mimeTypeCondition.append(')');
- superPrimaryUpdateSql += mimeTypeCondition.toString();
- db.execSQL(superPrimaryUpdateSql, args);
- }
-
- /**
- * @return JOIN if the raw contact of {@code rawContactId} can be joined into the existing
- * contact of {@code contactId}. KEEP_SEPARATE if the raw contact of {@code rawContactId}
- * cannot be joined into the existing contact of {@code contactId}. RE_AGGREGATE if raw contact
- * of {@code rawContactId} and all the raw contacts of contact of {@code contactId} need to be
- * re-aggregated.
- *
- * If contact of {@code contactId} doesn't contain any raw contacts from the same account as
- * raw contact of {@code rawContactId}, join raw contact with contact if there is no identity
- * mismatch between them on the same namespace, otherwise, keep them separate.
- *
- * If contact of {@code contactId} contains raw contacts from the same account as raw contact of
- * {@code rawContactId}, join raw contact with contact if there's at least one raw contact in
- * those raw contacts that shares at least one email address, phone number, or identity;
- * otherwise, re-aggregate raw contact and all the raw contacts of contact.
- */
- private int canJoinIntoContact(SQLiteDatabase db, long rawContactId,
- Set<Long> rawContactIdsInSameAccount, Set<Long> rawContactIdsInOtherAccount ) {
-
- if (rawContactIdsInSameAccount.isEmpty()) {
- final String rid = String.valueOf(rawContactId);
- final String ridsInOtherAccts = TextUtils.join(",", rawContactIdsInOtherAccount);
- // If there is no identity match between raw contact of [rawContactId] and
- // any raw contact in other accounts on the same namespace, and there is at least
- // one identity mismatch exist, keep raw contact separate from contact.
- if (DatabaseUtils.longForQuery(db, buildIdentityMatchingSql(rid, ridsInOtherAccts,
- /* isIdentityMatching =*/ true, /* countOnly =*/ true), null) == 0 &&
- DatabaseUtils.longForQuery(db, buildIdentityMatchingSql(rid, ridsInOtherAccts,
- /* isIdentityMatching =*/ false, /* countOnly =*/ true), null) > 0) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: no duplicates, but has no matching identity " +
- "and has mis-matching identity on the same namespace between rid=" +
- rid + " and ridsInOtherAccts=" + ridsInOtherAccts);
- }
- return KEEP_SEPARATE; // has identity and identity doesn't match
- } else {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: can join the first raw contact from the same " +
- "account without any identity mismatch.");
- }
- return JOIN; // no identity or identity match
- }
- }
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: " + rawContactIdsInSameAccount.size() +
- " duplicate(s) found");
- }
+ abstract void aggregateContact(TransactionContext txContext, SQLiteDatabase db,
+ long rawContactId, long accountId, long currentContactId,
+ MatchCandidateList candidates);
- final Set<Long> rawContactIdSet = new HashSet<Long>();
- rawContactIdSet.add(rawContactId);
- if (rawContactIdsInSameAccount.size() > 0 &&
- isDataMaching(db, rawContactIdSet, rawContactIdsInSameAccount)) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: join if there is a data matching found in the " +
- "same account");
- }
- return JOIN;
- } else {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: re-aggregate rid=" + rawContactId +
- " with its best matching contact to connected component");
- }
- return RE_AGGREGATE;
- }
- }
-
- private interface RawContactMatchingSelectionStatement {
- String SELECT_COUNT = "SELECT count(*) " ;
- String SELECT_ID = "SELECT d1." + Data.RAW_CONTACT_ID + ",d2." + Data.RAW_CONTACT_ID ;
+ protected interface RawContactMatchingSelectionStatement {
+ String SELECT_COUNT = "SELECT count(*) ";
+ String SELECT_ID = "SELECT d1." + Data.RAW_CONTACT_ID + ",d2." + Data.RAW_CONTACT_ID;
}
/**
* Build sql to check if there is any identity match/mis-match between two sets of raw contact
* ids on the same namespace.
*/
- private String buildIdentityMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
+ protected String buildIdentityMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
boolean isIdentityMatching, boolean countOnly) {
final String identityType = String.valueOf(mMimeTypeIdIdentity);
final String matchingOperator = (isIdentityMatching) ? "=" : "!=";
@@ -1044,7 +752,7 @@ public class AbstractContactAggregator {
RawContactMatchingSelectionStatement.SELECT_ID + sql;
}
- private String buildEmailMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
+ protected String buildEmailMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
boolean countOnly) {
final String emailType = String.valueOf(mMimeTypeIdEmail);
final String sql =
@@ -1059,7 +767,7 @@ public class AbstractContactAggregator {
RawContactMatchingSelectionStatement.SELECT_ID + sql;
}
- private String buildPhoneMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
+ protected String buildPhoneMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
boolean countOnly) {
// It's a bit tricker because it has to be consistent with
// updateMatchScoresBasedOnPhoneMatches().
@@ -1083,120 +791,29 @@ public class AbstractContactAggregator {
RawContactMatchingSelectionStatement.SELECT_ID + sql;
}
- private String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2) {
+ protected String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2) {
return "SELECT " + AggregationExceptions.RAW_CONTACT_ID1 + ", " +
AggregationExceptions.RAW_CONTACT_ID2 +
" FROM " + Tables.AGGREGATION_EXCEPTIONS +
" WHERE " + AggregationExceptions.RAW_CONTACT_ID1 + " IN (" +
- rawContactIdSet1 + ")" +
+ rawContactIdSet1 + ")" +
" AND " + AggregationExceptions.RAW_CONTACT_ID2 + " IN (" + rawContactIdSet2 + ")" +
" AND " + AggregationExceptions.TYPE + "=" +
- AggregationExceptions.TYPE_KEEP_TOGETHER ;
+ AggregationExceptions.TYPE_KEEP_TOGETHER ;
}
- private boolean isFirstColumnGreaterThanZero(SQLiteDatabase db, String query) {
+ protected boolean isFirstColumnGreaterThanZero(SQLiteDatabase db, String query) {
return DatabaseUtils.longForQuery(db, query, null) > 0;
}
/**
- * If there's any identity, email address or a phone number matching between two raw contact
- * sets.
- */
- private boolean isDataMaching(SQLiteDatabase db, Set<Long> rawContactIdSet1,
- Set<Long> rawContactIdSet2) {
- final String rawContactIds1 = TextUtils.join(",", rawContactIdSet1);
- final String rawContactIds2 = TextUtils.join(",", rawContactIdSet2);
- // First, check for the identity
- if (isFirstColumnGreaterThanZero(db, buildIdentityMatchingSql(
- rawContactIds1, rawContactIds2, /* isIdentityMatching =*/ true,
- /* countOnly =*/true))) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: identity match found between " + rawContactIds1 +
- " and " + rawContactIds2);
- }
- return true;
- }
-
- // Next, check for the email address.
- if (isFirstColumnGreaterThanZero(db,
- buildEmailMatchingSql(rawContactIds1, rawContactIds2, true))) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: email match found between " + rawContactIds1 +
- " and " + rawContactIds2);
- }
- return true;
- }
-
- // Lastly, the phone number.
- if (isFirstColumnGreaterThanZero(db,
- buildPhoneMatchingSql(rawContactIds1, rawContactIds2, true))) {
- if (VERBOSE_LOGGING) {
- Log.v(TAG, "canJoinIntoContact: phone match found between " + rawContactIds1 +
- " and " + rawContactIds2);
- }
- return true;
- }
- return false;
- }
-
- /**
- * Re-aggregate rawContact of {@code rawContactId} and all the raw contacts of
- * {@code existingRawContactIds} into connected components. This only happens when a given
- * raw contacts cannot be joined with its best matching contacts directly.
- *
- * Two raw contacts are considered connected if they share at least one email address, phone
- * number or identity. Create new contact for each connected component except the very first
- * one that doesn't contain rawContactId of {@code rawContactId}.
- */
- private void reAggregateRawContacts(TransactionContext txContext, SQLiteDatabase db,
- long contactId, long currentContactId, long rawContactId,
- Set<Long> existingRawContactIds) {
- // Find the connected component based on the aggregation exceptions or
- // identity/email/phone matching for all the raw contacts of [contactId] and the give
- // raw contact.
- final Set<Long> allIds = new HashSet<Long>();
- allIds.add(rawContactId);
- allIds.addAll(existingRawContactIds);
- final Set<Set<Long>> connectedRawContactSets = findConnectedRawContacts(db, allIds);
-
- if (connectedRawContactSets.size() == 1) {
- // If everything is connected, create one contact with [contactId]
- createContactForRawContacts(db, txContext, connectedRawContactSets.iterator().next(),
- contactId);
- } else {
- for (Set<Long> connectedRawContactIds : connectedRawContactSets) {
- if (connectedRawContactIds.contains(rawContactId)) {
- // crate contact for connect component containing [rawContactId], reuse
- // [currentContactId] if possible.
- createContactForRawContacts(db, txContext, connectedRawContactIds,
- currentContactId == 0 ? null : currentContactId);
- connectedRawContactSets.remove(connectedRawContactIds);
- break;
- }
- }
- // Create new contact for each connected component except the last one. The last one
- // will reuse [contactId]. Only the last one can reuse [contactId] when all other raw
- // contacts has already been assigned new contact Id, so that the contact aggregation
- // stats could be updated correctly.
- int index = connectedRawContactSets.size();
- for (Set<Long> connectedRawContactIds : connectedRawContactSets) {
- if (index > 1) {
- createContactForRawContacts(db, txContext, connectedRawContactIds, null);
- index--;
- } else {
- createContactForRawContacts(db, txContext, connectedRawContactIds, contactId);
- }
- }
- }
- }
-
- /**
* Partition the given raw contact Ids to connected component based on aggregation exception,
* identity matching, email matching or phone matching.
*/
- private Set<Set<Long>> findConnectedRawContacts(SQLiteDatabase db, Set<Long> rawContactIdSet) {
+ protected Set<Set<Long>> findConnectedRawContacts(SQLiteDatabase db, Set<Long>
+ rawContactIdSet) {
// Connections between two raw contacts
- final Multimap<Long, Long> matchingRawIdPairs = HashMultimap.create();
+ final Multimap<Long, Long> matchingRawIdPairs = HashMultimap.create();
String rawContactIds = TextUtils.join(",", rawContactIdSet);
findIdPairs(db, buildExceptionMatchingSql(rawContactIds, rawContactIds),
matchingRawIdPairs);
@@ -1215,7 +832,7 @@ public class AbstractContactAggregator {
* method will put two entries into the given result map for each pair of different IDs, one
* keyed by each ID.
*/
- private void findIdPairs(SQLiteDatabase db, String query, Multimap<Long, Long> results) {
+ protected void findIdPairs(SQLiteDatabase db, String query, Multimap<Long, Long> results) {
Cursor cursor = db.rawQuery(query, null);
try {
cursor.moveToPosition(-1);
@@ -1236,7 +853,7 @@ public class AbstractContactAggregator {
* Creates a new Contact for a given set of the raw contacts of {@code rawContactIds} if the
* given contactId is null. Otherwise, regroup them into contact with {@code contactId}.
*/
- private void createContactForRawContacts(SQLiteDatabase db, TransactionContext txContext,
+ protected void createContactForRawContacts(SQLiteDatabase db, TransactionContext txContext,
Set<Long> rawContactIds, Long contactId) {
if (rawContactIds.isEmpty()) {
// No raw contact id is provided.
@@ -1245,7 +862,7 @@ public class AbstractContactAggregator {
// If contactId is not provided, generates a new one.
if (contactId == null) {
- mSelectionArgs1[0]= String.valueOf(rawContactIds.iterator().next());
+ mSelectionArgs1[0] = String.valueOf(rawContactIds.iterator().next());
computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1,
mContactInsert);
contactId = mContactInsert.executeInsert();
@@ -1259,66 +876,12 @@ public class AbstractContactAggregator {
updateAggregateData(txContext, contactId);
}
- private static class RawContactIdQuery {
+ protected static class RawContactIdQuery {
public static final String TABLE = Tables.RAW_CONTACTS;
- public static final String[] COLUMNS = { RawContacts._ID };
+ public static final String[] COLUMNS = {RawContacts._ID, RawContactsColumns.ACCOUNT_ID };
public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
public static final int RAW_CONTACT_ID = 0;
- }
-
- /**
- * Ensures that automatic aggregation rules are followed after a contact
- * becomes visible or invisible. Specifically, consider this case: there are
- * three contacts named Foo. Two of them come from account A1 and one comes
- * from account A2. The aggregation rules say that in this case none of the
- * three Foo's should be aggregated: two of them are in the same account, so
- * they don't get aggregated; the third has two affinities, so it does not
- * join either of them.
- * <p>
- * Consider what happens if one of the "Foo"s from account A1 becomes
- * invisible. Nothing stands in the way of aggregating the other two
- * anymore, so they should get joined.
- * <p>
- * What if the invisible "Foo" becomes visible after that? We should split the
- * aggregate between the other two.
- */
- public void updateAggregationAfterVisibilityChange(long contactId) {
- SQLiteDatabase db = mDbHelper.getWritableDatabase();
- boolean visible = mDbHelper.isContactInDefaultDirectory(db, contactId);
- if (visible) {
- markContactForAggregation(db, contactId);
- } else {
- // Find all contacts that _could be_ aggregated with this one and
- // rerun aggregation for all of them
- mSelectionArgs1[0] = String.valueOf(contactId);
- Cursor cursor = db.query(RawContactIdQuery.TABLE, RawContactIdQuery.COLUMNS,
- RawContactIdQuery.SELECTION, mSelectionArgs1, null, null, null);
- try {
- while (cursor.moveToNext()) {
- long rawContactId = cursor.getLong(RawContactIdQuery.RAW_CONTACT_ID);
- mMatcher.clear();
-
- updateMatchScoresBasedOnIdentityMatch(db, rawContactId, mMatcher);
- updateMatchScoresBasedOnNameMatches(db, rawContactId, mMatcher);
- List<MatchScore> bestMatches =
- mMatcher.pickBestMatches(ContactMatcher.SCORE_THRESHOLD_PRIMARY);
- for (MatchScore matchScore : bestMatches) {
- markContactForAggregation(db, matchScore.getContactId());
- }
-
- mMatcher.clear();
- updateMatchScoresBasedOnEmailMatches(db, rawContactId, mMatcher);
- updateMatchScoresBasedOnPhoneMatches(db, rawContactId, mMatcher);
- bestMatches =
- mMatcher.pickBestMatches(ContactMatcher.SCORE_THRESHOLD_SECONDARY);
- for (MatchScore matchScore : bestMatches) {
- markContactForAggregation(db, matchScore.getContactId());
- }
- }
- } finally {
- cursor.close();
- }
- }
+ public static final int ACCOUNT_ID = 1;
}
/**
@@ -1333,7 +896,7 @@ public class AbstractContactAggregator {
/**
* Marks the specified raw contact ID as aggregated
*/
- private void markAggregated(long rawContactId) {
+ protected void markAggregated(long rawContactId) {
mMarkAggregatedUpdate.bindLong(1, rawContactId);
mMarkAggregatedUpdate.execute();
}
@@ -1362,8 +925,8 @@ public class AbstractContactAggregator {
String TABLE = Tables.AGGREGATION_EXCEPTIONS;
String[] COLUMNS = {
- AggregationExceptions.RAW_CONTACT_ID1,
- AggregationExceptions.RAW_CONTACT_ID2,
+ AggregationExceptions.RAW_CONTACT_ID1,
+ AggregationExceptions.RAW_CONTACT_ID2,
};
int RAW_CONTACT_ID1 = 0;
@@ -1371,8 +934,8 @@ public class AbstractContactAggregator {
}
// A set of raw contact IDs for which there are aggregation exceptions
- private final HashSet<Long> mAggregationExceptionIds = new HashSet<Long>();
- private boolean mAggregationExceptionIdsValid;
+ protected final HashSet<Long> mAggregationExceptionIds = new HashSet<Long>();
+ protected boolean mAggregationExceptionIdsValid;
public void invalidateAggregationExceptionCache() {
mAggregationExceptionIdsValid = false;
@@ -1384,7 +947,7 @@ public class AbstractContactAggregator {
* the agg_exceptions table if it is known that there are no records there for a given
* raw contact ID.
*/
- private void prefetchAggregationExceptionIds(SQLiteDatabase db) {
+ protected void prefetchAggregationExceptionIds(SQLiteDatabase db) {
mAggregationExceptionIds.clear();
final Cursor c = db.query(AggregateExceptionPrefetchQuery.TABLE,
AggregateExceptionPrefetchQuery.COLUMNS,
@@ -1404,152 +967,7 @@ public class AbstractContactAggregator {
mAggregationExceptionIdsValid = true;
}
- interface AggregateExceptionQuery {
- String TABLE = Tables.AGGREGATION_EXCEPTIONS
- + " JOIN raw_contacts raw_contacts1 "
- + " ON (agg_exceptions.raw_contact_id1 = raw_contacts1._id) "
- + " JOIN raw_contacts raw_contacts2 "
- + " ON (agg_exceptions.raw_contact_id2 = raw_contacts2._id) ";
-
- String[] COLUMNS = {
- AggregationExceptions.TYPE,
- AggregationExceptions.RAW_CONTACT_ID1,
- "raw_contacts1." + RawContacts.CONTACT_ID,
- "raw_contacts1." + RawContactsColumns.AGGREGATION_NEEDED,
- "raw_contacts2." + RawContacts.CONTACT_ID,
- "raw_contacts2." + RawContactsColumns.AGGREGATION_NEEDED,
- };
-
- int TYPE = 0;
- int RAW_CONTACT_ID1 = 1;
- int CONTACT_ID1 = 2;
- int AGGREGATION_NEEDED_1 = 3;
- int CONTACT_ID2 = 4;
- int AGGREGATION_NEEDED_2 = 5;
- }
-
- /**
- * Computes match scores based on exceptions entered by the user: always match and never match.
- * Returns the aggregate contact with the always match exception if any.
- */
- private long pickBestMatchBasedOnExceptions(SQLiteDatabase db, long rawContactId,
- ContactMatcher matcher) {
- if (!mAggregationExceptionIdsValid) {
- prefetchAggregationExceptionIds(db);
- }
-
- // If there are no aggregation exceptions involving this raw contact, there is no need to
- // run a query and we can just return -1, which stands for "nothing found"
- if (!mAggregationExceptionIds.contains(rawContactId)) {
- return -1;
- }
-
- final Cursor c = db.query(AggregateExceptionQuery.TABLE,
- AggregateExceptionQuery.COLUMNS,
- AggregationExceptions.RAW_CONTACT_ID1 + "=" + rawContactId
- + " OR " + AggregationExceptions.RAW_CONTACT_ID2 + "=" + rawContactId,
- null, null, null, null);
-
- try {
- while (c.moveToNext()) {
- int type = c.getInt(AggregateExceptionQuery.TYPE);
- long rawContactId1 = c.getLong(AggregateExceptionQuery.RAW_CONTACT_ID1);
- long contactId = -1;
- if (rawContactId == rawContactId1) {
- if (c.getInt(AggregateExceptionQuery.AGGREGATION_NEEDED_2) == 0
- && !c.isNull(AggregateExceptionQuery.CONTACT_ID2)) {
- contactId = c.getLong(AggregateExceptionQuery.CONTACT_ID2);
- }
- } else {
- if (c.getInt(AggregateExceptionQuery.AGGREGATION_NEEDED_1) == 0
- && !c.isNull(AggregateExceptionQuery.CONTACT_ID1)) {
- contactId = c.getLong(AggregateExceptionQuery.CONTACT_ID1);
- }
- }
- if (contactId != -1) {
- if (type == AggregationExceptions.TYPE_KEEP_TOGETHER) {
- matcher.keepIn(contactId);
- } else {
- matcher.keepOut(contactId);
- }
- }
- }
- } finally {
- c.close();
- }
-
- return matcher.pickBestMatch(ContactMatcher.MAX_SCORE, true);
- }
-
- /**
- * Picks the best matching contact based on matches between data elements. It considers
- * name match to be primary and phone, email etc matches to be secondary. A good primary
- * match triggers aggregation, while a good secondary match only triggers aggregation in
- * the absence of a strong primary mismatch.
- * <p>
- * Consider these examples:
- * <p>
- * John Doe with phone number 111-111-1111 and Jon Doe with phone number 111-111-1111 should
- * be aggregated (same number, similar names).
- * <p>
- * John Doe with phone number 111-111-1111 and Deborah Doe with phone number 111-111-1111 should
- * not be aggregated (same number, different names).
- */
- private long pickBestMatchBasedOnData(SQLiteDatabase db, long rawContactId,
- MatchCandidateList candidates, ContactMatcher matcher) {
-
- // Find good matches based on name alone
- long bestMatch = updateMatchScoresBasedOnDataMatches(db, rawContactId, matcher);
- 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;
- }
-
-
- /**
- * Picks the best matching contact based on secondary data matches. The method loads
- * structured names for all candidate contacts and recomputes match scores using approximate
- * matching.
- */
- private long pickBestMatchBasedOnSecondaryData(SQLiteDatabase db,
- long rawContactId, MatchCandidateList candidates, ContactMatcher matcher) {
- List<Long> secondaryContactIds = matcher.prepareSecondaryMatchCandidates(
- ContactMatcher.SCORE_THRESHOLD_PRIMARY);
- if (secondaryContactIds == null || secondaryContactIds.size() > SECONDARY_HIT_LIMIT) {
- return -1;
- }
-
- loadNameMatchCandidates(db, rawContactId, candidates, true);
-
- mSb.setLength(0);
- mSb.append(RawContacts.CONTACT_ID).append(" IN (");
- for (int i = 0; i < secondaryContactIds.size(); i++) {
- if (i != 0) {
- mSb.append(',');
- }
- mSb.append(secondaryContactIds.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,
- ContactMatcher.MATCHING_ALGORITHM_CONSERVATIVE, null);
-
- return matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_SECONDARY, false);
- }
-
- private interface NameLookupQuery {
+ protected interface NameLookupQuery {
String TABLE = Tables.NAME_LOOKUP;
String SELECTION = NameLookupColumns.RAW_CONTACT_ID + "=?";
@@ -1565,7 +983,7 @@ public class AbstractContactAggregator {
int NAME_TYPE = 1;
}
- private void loadNameMatchCandidates(SQLiteDatabase db, long rawContactId,
+ protected void loadNameMatchCandidates(SQLiteDatabase db, long rawContactId,
MatchCandidateList candidates, boolean structuredNameBased) {
candidates.clear();
mSelectionArgs1[0] = String.valueOf(rawContactId);
@@ -1585,26 +1003,7 @@ public class AbstractContactAggregator {
}
}
- /**
- * Computes scores for contacts that have matching data rows.
- */
- private long updateMatchScoresBasedOnDataMatches(SQLiteDatabase db, long rawContactId,
- ContactMatcher matcher) {
-
- updateMatchScoresBasedOnIdentityMatch(db, rawContactId, matcher);
- updateMatchScoresBasedOnNameMatches(db, rawContactId, matcher);
- long bestMatch = matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_PRIMARY, false);
- if (bestMatch != -1) {
- return bestMatch;
- }
-
- updateMatchScoresBasedOnEmailMatches(db, rawContactId, matcher);
- updateMatchScoresBasedOnPhoneMatches(db, rawContactId, matcher);
-
- return -1;
- }
-
- private interface IdentityLookupMatchQuery {
+ protected interface IdentityLookupMatchQuery {
final String TABLE = Tables.DATA + " dataA"
+ " JOIN " + Tables.DATA + " dataB" +
" ON (dataA." + Identity.NAMESPACE + "=dataB." + Identity.NAMESPACE +
@@ -1622,34 +1021,45 @@ public class AbstractContactAggregator {
+ " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
final String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID
+ RawContacts._ID, RawContacts.CONTACT_ID, RawContactsColumns.ACCOUNT_ID
};
- int CONTACT_ID = 0;
+ int RAW_CONTACT_ID = 0;
+ int CONTACT_ID = 1;
+ int ACCOUNT_ID = 2;
}
- /**
- * Finds contacts with exact identity matches to the the specified raw contact.
- */
- private void updateMatchScoresBasedOnIdentityMatch(SQLiteDatabase db, long rawContactId,
- ContactMatcher matcher) {
- mSelectionArgs2[0] = String.valueOf(rawContactId);
- mSelectionArgs2[1] = String.valueOf(mMimeTypeIdIdentity);
- Cursor c = db.query(IdentityLookupMatchQuery.TABLE, IdentityLookupMatchQuery.COLUMNS,
- IdentityLookupMatchQuery.SELECTION,
- mSelectionArgs2, RawContacts.CONTACT_ID, null, null);
- try {
- while (c.moveToNext()) {
- final long contactId = c.getLong(IdentityLookupMatchQuery.CONTACT_ID);
- matcher.matchIdentity(contactId);
- }
- } finally {
- c.close();
- }
+ interface AggregateExceptionQuery {
+ String TABLE = Tables.AGGREGATION_EXCEPTIONS
+ + " JOIN raw_contacts raw_contacts1 "
+ + " ON (agg_exceptions.raw_contact_id1 = raw_contacts1._id) "
+ + " JOIN raw_contacts raw_contacts2 "
+ + " ON (agg_exceptions.raw_contact_id2 = raw_contacts2._id) ";
+ String[] COLUMNS = {
+ AggregationExceptions.TYPE,
+ AggregationExceptions.RAW_CONTACT_ID1,
+ "raw_contacts1." + RawContacts.CONTACT_ID,
+ "raw_contacts1." + RawContactsColumns.ACCOUNT_ID,
+ "raw_contacts1." + RawContactsColumns.AGGREGATION_NEEDED,
+ AggregationExceptions.RAW_CONTACT_ID2,
+ "raw_contacts2." + RawContacts.CONTACT_ID,
+ "raw_contacts2." + RawContactsColumns.ACCOUNT_ID,
+ "raw_contacts2." + RawContactsColumns.AGGREGATION_NEEDED,
+ };
+
+ int TYPE = 0;
+ int RAW_CONTACT_ID1 = 1;
+ int CONTACT_ID1 = 2;
+ int ACCOUNT_ID1 = 3;
+ int AGGREGATION_NEEDED_1 = 4;
+ int RAW_CONTACT_ID2 = 5;
+ int CONTACT_ID2 = 6;
+ int ACCOUNT_ID2 = 7;
+ int AGGREGATION_NEEDED_2 = 8;
}
- private interface NameLookupMatchQuery {
+ protected interface NameLookupMatchQuery {
String TABLE = Tables.NAME_LOOKUP + " nameA"
+ " JOIN " + Tables.NAME_LOOKUP + " nameB" +
" ON (" + "nameA." + NameLookupColumns.NORMALIZED_NAME + "="
@@ -1663,63 +1073,44 @@ public class AbstractContactAggregator {
+ " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID,
- "nameA." + NameLookupColumns.NORMALIZED_NAME,
- "nameA." + NameLookupColumns.NAME_TYPE,
- "nameB." + NameLookupColumns.NAME_TYPE,
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContactsColumns.ACCOUNT_ID,
+ "nameA." + NameLookupColumns.NORMALIZED_NAME,
+ "nameA." + NameLookupColumns.NAME_TYPE,
+ "nameB." + NameLookupColumns.NAME_TYPE,
};
- int CONTACT_ID = 0;
- int NAME = 1;
- int NAME_TYPE_A = 2;
- int NAME_TYPE_B = 3;
- }
-
- /**
- * Finds contacts with names matching the name of the specified raw contact.
- */
- private void updateMatchScoresBasedOnNameMatches(SQLiteDatabase db, long rawContactId,
- ContactMatcher matcher) {
- mSelectionArgs1[0] = String.valueOf(rawContactId);
- Cursor c = db.query(NameLookupMatchQuery.TABLE, NameLookupMatchQuery.COLUMNS,
- NameLookupMatchQuery.SELECTION,
- mSelectionArgs1, null, null, null, PRIMARY_HIT_LIMIT_STRING);
- try {
- while (c.moveToNext()) {
- long contactId = c.getLong(NameLookupMatchQuery.CONTACT_ID);
- String name = c.getString(NameLookupMatchQuery.NAME);
- int nameTypeA = c.getInt(NameLookupMatchQuery.NAME_TYPE_A);
- int nameTypeB = c.getInt(NameLookupMatchQuery.NAME_TYPE_B);
- matcher.matchName(contactId, nameTypeA, name,
- nameTypeB, name, ContactMatcher.MATCHING_ALGORITHM_EXACT);
- if (nameTypeA == NameLookupType.NICKNAME &&
- nameTypeB == NameLookupType.NICKNAME) {
- matcher.updateScoreWithNicknameMatch(contactId);
- }
- }
- } finally {
- c.close();
- }
+ int RAW_CONTACT_ID = 0;
+ int CONTACT_ID = 1;
+ int ACCOUNT_ID = 2;
+ int NAME = 3;
+ int NAME_TYPE_A = 4;
+ int NAME_TYPE_B = 5;
}
- private interface NameLookupMatchQueryWithParameter {
+ protected interface NameLookupMatchQueryWithParameter {
String TABLE = Tables.NAME_LOOKUP
+ " JOIN " + Tables.RAW_CONTACTS +
" ON (" + NameLookupColumns.RAW_CONTACT_ID + " = "
+ Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID,
- NameLookupColumns.NORMALIZED_NAME,
- NameLookupColumns.NAME_TYPE,
+ RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContactsColumns.ACCOUNT_ID,
+ NameLookupColumns.NORMALIZED_NAME,
+ NameLookupColumns.NAME_TYPE,
};
- int CONTACT_ID = 0;
- int NAME = 1;
- int NAME_TYPE = 2;
+ int RAW_CONTACT_ID = 0;
+ int CONTACT_ID = 1;
+ int ACCOUNT_ID = 2;
+ int NAME = 3;
+ int NAME_TYPE = 4;
}
- private final class NameLookupSelectionBuilder extends NameLookupBuilder {
+ protected final class NameLookupSelectionBuilder extends NameLookupBuilder {
private final MatchCandidateList mNameLookupCandidates;
@@ -1768,7 +1159,7 @@ public class AbstractContactAggregator {
/**
* Finds contacts with names matching the specified name.
*/
- private void updateMatchScoresBasedOnNameMatches(SQLiteDatabase db, String query,
+ protected void updateMatchScoresBasedOnNameMatches(SQLiteDatabase db, String query,
MatchCandidateList candidates, ContactMatcher matcher) {
candidates.clear();
NameLookupSelectionBuilder builder = new NameLookupSelectionBuilder(
@@ -1798,7 +1189,7 @@ public class AbstractContactAggregator {
}
}
- private interface EmailLookupQuery {
+ protected interface EmailLookupQuery {
String TABLE = Tables.DATA + " dataA"
+ " JOIN " + Tables.DATA + " dataB" +
" ON lower(" + "dataA." + Email.DATA + ")=lower(dataB." + Email.DATA + ")"
@@ -1814,30 +1205,17 @@ public class AbstractContactAggregator {
+ " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID
+ Tables.RAW_CONTACTS + "." + RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContactsColumns.ACCOUNT_ID
};
- int CONTACT_ID = 0;
- }
-
- private void updateMatchScoresBasedOnEmailMatches(SQLiteDatabase db, long rawContactId,
- ContactMatcher matcher) {
- mSelectionArgs2[0] = String.valueOf(rawContactId);
- mSelectionArgs2[1] = String.valueOf(mMimeTypeIdEmail);
- Cursor c = db.query(EmailLookupQuery.TABLE, EmailLookupQuery.COLUMNS,
- EmailLookupQuery.SELECTION,
- mSelectionArgs2, null, null, null, SECONDARY_HIT_LIMIT_STRING);
- try {
- while (c.moveToNext()) {
- long contactId = c.getLong(EmailLookupQuery.CONTACT_ID);
- matcher.updateScoreWithEmailMatch(contactId);
- }
- } finally {
- c.close();
- }
+ int RAW_CONTACT_ID = 0;
+ int CONTACT_ID = 1;
+ int ACCOUNT_ID = 2;
}
- private interface PhoneLookupQuery {
+ protected interface PhoneLookupQuery {
String TABLE = Tables.PHONE_LOOKUP + " phoneA"
+ " JOIN " + Tables.DATA + " dataA"
+ " ON (dataA." + Data._ID + "=phoneA." + PhoneLookupColumns.DATA_ID + ")"
@@ -1857,61 +1235,20 @@ public class AbstractContactAggregator {
+ " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID
+ Tables.RAW_CONTACTS + "." + RawContacts._ID,
+ RawContacts.CONTACT_ID,
+ RawContactsColumns.ACCOUNT_ID
};
- int CONTACT_ID = 0;
- }
-
- private void updateMatchScoresBasedOnPhoneMatches(SQLiteDatabase db, long rawContactId,
- ContactMatcher matcher) {
- mSelectionArgs2[0] = String.valueOf(rawContactId);
- mSelectionArgs2[1] = mDbHelper.getUseStrictPhoneNumberComparisonParameter();
- Cursor c = db.query(PhoneLookupQuery.TABLE, PhoneLookupQuery.COLUMNS,
- PhoneLookupQuery.SELECTION,
- mSelectionArgs2, null, null, null, SECONDARY_HIT_LIMIT_STRING);
- try {
- while (c.moveToNext()) {
- long contactId = c.getLong(PhoneLookupQuery.CONTACT_ID);
- matcher.updateScoreWithPhoneNumberMatch(contactId);
- }
- } finally {
- c.close();
- }
- }
-
- /**
- * Loads name lookup rows for approximate name matching and updates match scores based on that
- * data.
- */
- private void lookupApproximateNameMatches(SQLiteDatabase db, MatchCandidateList candidates,
- ContactMatcher matcher) {
- HashSet<String> firstLetters = new HashSet<String>();
- for (int i = 0; i < candidates.mCount; i++) {
- final NameMatchCandidate candidate = candidates.mList.get(i);
- if (candidate.mName.length() >= 2) {
- String firstLetter = candidate.mName.substring(0, 2);
- if (!firstLetters.contains(firstLetter)) {
- firstLetters.add(firstLetter);
- final String selection = "(" + NameLookupColumns.NORMALIZED_NAME + " GLOB '"
- + firstLetter + "*') AND "
- + "(" + NameLookupColumns.NAME_TYPE + " IN("
- + NameLookupType.NAME_COLLATION_KEY + ","
- + NameLookupType.EMAIL_BASED_NICKNAME + ","
- + NameLookupType.NICKNAME + ")) AND "
- + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
- matchAllCandidates(db, selection, candidates, matcher,
- ContactMatcher.MATCHING_ALGORITHM_APPROXIMATE,
- String.valueOf(FIRST_LETTER_SUGGESTION_HIT_LIMIT));
- }
- }
- }
+ int RAW_CONTACT_ID = 0;
+ int CONTACT_ID = 1;
+ int ACCOUNT_ID = 2;
}
private interface ContactNameLookupQuery {
String TABLE = Tables.NAME_LOOKUP_JOIN_RAW_CONTACTS;
- String[] COLUMNS = new String[] {
+ String[] COLUMNS = new String[]{
RawContacts.CONTACT_ID,
NameLookupColumns.NORMALIZED_NAME,
NameLookupColumns.NAME_TYPE
@@ -2017,7 +1354,7 @@ public class AbstractContactAggregator {
int HAS_SUPER_PRIMARY_NAME = 17;
}
- private interface ContactReplaceSqlStatement {
+ protected interface ContactReplaceSqlStatement {
String UPDATE_SQL =
"UPDATE " + Tables.CONTACTS +
" SET "
@@ -2070,7 +1407,7 @@ public class AbstractContactAggregator {
/**
* Computes aggregate-level data for the specified aggregate contact ID.
*/
- private void computeAggregateData(SQLiteDatabase db, long contactId,
+ protected void computeAggregateData(SQLiteDatabase db, long contactId,
SQLiteStatement statement) {
mSelectionArgs1[0] = String.valueOf(contactId);
computeAggregateData(db, mRawContactsQueryByContactId, mSelectionArgs1, statement);
@@ -2089,7 +1426,7 @@ public class AbstractContactAggregator {
/**
* Computes aggregate-level data from constituent raw contacts.
*/
- private void computeAggregateData(final SQLiteDatabase db, String sql, String[] sqlArgs,
+ protected void computeAggregateData(final SQLiteDatabase db, String sql, String[] sqlArgs,
SQLiteStatement statement) {
long currentRawContactId = -1;
long bestPhotoId = -1;
@@ -2748,58 +2085,8 @@ public class AbstractContactAggregator {
* descending order of match score.
* @param parameters
*/
- private List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId,
- ArrayList<AggregationSuggestionParameter> parameters) {
-
- MatchCandidateList candidates = new MatchCandidateList();
- ContactMatcher matcher = new ContactMatcher();
-
- // Don't aggregate a contact with itself
- matcher.keepOut(contactId);
-
- if (parameters == null || parameters.size() == 0) {
- final Cursor c = db.query(RawContactIdQuery.TABLE, RawContactIdQuery.COLUMNS,
- RawContacts.CONTACT_ID + "=" + contactId, null, null, null, null);
- try {
- while (c.moveToNext()) {
- long rawContactId = c.getLong(RawContactIdQuery.RAW_CONTACT_ID);
- updateMatchScoresForSuggestionsBasedOnDataMatches(db, rawContactId, candidates,
- matcher);
- }
- } finally {
- c.close();
- }
- } else {
- updateMatchScoresForSuggestionsBasedOnDataMatches(db, candidates,
- matcher, parameters);
- }
-
- return matcher.pickBestMatches(ContactMatcher.SCORE_THRESHOLD_SUGGEST);
- }
-
- /**
- * Computes scores for contacts that have matching data rows.
- */
- private void updateMatchScoresForSuggestionsBasedOnDataMatches(SQLiteDatabase db,
- long rawContactId, MatchCandidateList candidates, ContactMatcher matcher) {
+ protected abstract List<MatchScore> findMatchingContacts(final SQLiteDatabase db,
+ long contactId, ArrayList<AggregationSuggestionParameter> parameters);
- updateMatchScoresBasedOnIdentityMatch(db, rawContactId, matcher);
- updateMatchScoresBasedOnNameMatches(db, rawContactId, matcher);
- updateMatchScoresBasedOnEmailMatches(db, rawContactId, matcher);
- updateMatchScoresBasedOnPhoneMatches(db, rawContactId, matcher);
- loadNameMatchCandidates(db, rawContactId, candidates, false);
- lookupApproximateNameMatches(db, candidates, matcher);
- }
-
- private void updateMatchScoresForSuggestionsBasedOnDataMatches(SQLiteDatabase db,
- MatchCandidateList candidates, ContactMatcher matcher,
- ArrayList<AggregationSuggestionParameter> parameters) {
- for (AggregationSuggestionParameter parameter : parameters) {
- if (AggregationSuggestions.PARAMETER_MATCH_NAME.equals(parameter.kind)) {
- updateMatchScoresBasedOnNameMatches(db, parameter.value, candidates, matcher);
- }
-
- // TODO: add support for other parameter kinds
- }
- }
+ public abstract void updateAggregationAfterVisibilityChange(long contactId);
}
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator.java b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
index 253adf0..d2108b6 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator.java
@@ -19,68 +19,32 @@ package com.android.providers.contacts.aggregation;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.database.sqlite.SQLiteStatement;
-import android.net.Uri;
import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Identity;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
-import android.provider.ContactsContract.FullNameStyle;
-import android.provider.ContactsContract.PhotoFiles;
-import android.provider.ContactsContract.PinnedPositions;
import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
import android.text.TextUtils;
-import android.util.EventLog;
import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.providers.contacts.ContactLookupKey;
import com.android.providers.contacts.ContactsDatabaseHelper;
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
-import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.Views;
import com.android.providers.contacts.ContactsProvider2;
-import com.android.providers.contacts.NameLookupBuilder;
-import com.android.providers.contacts.NameNormalizer;
import com.android.providers.contacts.NameSplitter;
import com.android.providers.contacts.PhotoPriorityResolver;
-import com.android.providers.contacts.ReorderingCursorWrapper;
import com.android.providers.contacts.TransactionContext;
import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
-import com.android.providers.contacts.aggregation.util.ContactAggregatorHelper;
import com.android.providers.contacts.aggregation.util.ContactMatcher;
-import com.android.providers.contacts.aggregation.util.ContactMatcher.MatchScore;
+import com.android.providers.contacts.aggregation.util.MatchScore;
import com.android.providers.contacts.database.ContactsTableUtil;
-import com.android.providers.contacts.util.Clock;
-
-import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.HashMultimap;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Set;
/**
@@ -88,194 +52,14 @@ import java.util.Set;
* Two John Doe contacts from two disjoint sources are presumed to be the same
* person unless the user declares otherwise.
*/
-public class ContactAggregator {
-
- private static final String TAG = "ContactAggregator";
-
- private static final boolean DEBUG_LOGGING = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
-
- private static final String STRUCTURED_NAME_BASED_LOOKUP_SQL =
- NameLookupColumns.NAME_TYPE + " IN ("
- + NameLookupType.NAME_EXACT + ","
- + NameLookupType.NAME_VARIANT + ","
- + NameLookupType.NAME_COLLATION_KEY + ")";
-
-
- /**
- * SQL statement that sets the {@link ContactsColumns#LAST_STATUS_UPDATE_ID} column
- * on the contact to point to the latest social status update.
- */
- private static final String UPDATE_LAST_STATUS_UPDATE_ID_SQL =
- "UPDATE " + Tables.CONTACTS +
- " SET " + ContactsColumns.LAST_STATUS_UPDATE_ID + "=" +
- "(SELECT " + DataColumns.CONCRETE_ID +
- " FROM " + Tables.STATUS_UPDATES +
- " JOIN " + Tables.DATA +
- " ON (" + StatusUpdatesColumns.DATA_ID + "="
- + DataColumns.CONCRETE_ID + ")" +
- " JOIN " + Tables.RAW_CONTACTS +
- " ON (" + DataColumns.CONCRETE_RAW_CONTACT_ID + "="
- + RawContactsColumns.CONCRETE_ID + ")" +
- " WHERE " + RawContacts.CONTACT_ID + "=?" +
- " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC,"
- + StatusUpdates.STATUS +
- " LIMIT 1)" +
- " WHERE " + ContactsColumns.CONCRETE_ID + "=?";
-
- // From system/core/logcat/event-log-tags
- // aggregator [time, count] will be logged for each aggregator cycle.
- // For the query (as opposed to the merge), count will be negative
- public static final int LOG_SYNC_CONTACTS_AGGREGATION = 2747;
-
- // If we encounter more than this many contacts with matching names, aggregate only this many
- private static final int PRIMARY_HIT_LIMIT = 15;
- private static final String PRIMARY_HIT_LIMIT_STRING = String.valueOf(PRIMARY_HIT_LIMIT);
-
- // If we encounter more than this many contacts with matching phone number or email,
- // don't attempt to aggregate - this is likely an error or a shared corporate data element.
- private static final int SECONDARY_HIT_LIMIT = 20;
- private static final String SECONDARY_HIT_LIMIT_STRING = String.valueOf(SECONDARY_HIT_LIMIT);
-
- // If we encounter no less than this many raw contacts in the best matching contact during
- // aggregation, don't attempt to aggregate - this is likely an error or a shared corporate
- // data element.
- @VisibleForTesting
- static final int AGGREGATION_CONTACT_SIZE_LIMIT = 50;
-
- // If we encounter more than this many contacts with matching name during aggregation
- // suggestion lookup, ignore the remaining results.
- private static final int FIRST_LETTER_SUGGESTION_HIT_LIMIT = 100;
+public class ContactAggregator extends AbstractContactAggregator {
// Return code for the canJoinIntoContact method.
private static final int JOIN = 1;
private static final int KEEP_SEPARATE = 0;
private static final int RE_AGGREGATE = -1;
- private final ContactsProvider2 mContactsProvider;
- private final ContactsDatabaseHelper mDbHelper;
- private PhotoPriorityResolver mPhotoPriorityResolver;
- private final NameSplitter mNameSplitter;
- private final CommonNicknameCache mCommonNicknameCache;
-
- private boolean mEnabled = true;
-
- /** Precompiled sql statement for setting an aggregated presence */
- private SQLiteStatement mAggregatedPresenceReplace;
- private SQLiteStatement mPresenceContactIdUpdate;
- private SQLiteStatement mRawContactCountQuery;
- private SQLiteStatement mAggregatedPresenceDelete;
- private SQLiteStatement mMarkForAggregation;
- private SQLiteStatement mPhotoIdUpdate;
- private SQLiteStatement mDisplayNameUpdate;
- private SQLiteStatement mLookupKeyUpdate;
- private SQLiteStatement mStarredUpdate;
- private SQLiteStatement mPinnedUpdate;
- private SQLiteStatement mContactIdAndMarkAggregatedUpdate;
- private SQLiteStatement mContactIdUpdate;
- private SQLiteStatement mMarkAggregatedUpdate;
- private SQLiteStatement mContactUpdate;
- private SQLiteStatement mContactInsert;
- private SQLiteStatement mResetPinnedForRawContact;
-
- private HashMap<Long, Integer> mRawContactsMarkedForAggregation = Maps.newHashMap();
-
- private String[] mSelectionArgs1 = new String[1];
- private String[] mSelectionArgs2 = new String[2];
-
- private long mMimeTypeIdIdentity;
- private long mMimeTypeIdEmail;
- private long mMimeTypeIdPhoto;
- private long mMimeTypeIdPhone;
- private String mRawContactsQueryByRawContactId;
- private String mRawContactsQueryByContactId;
- private StringBuilder mSb = new StringBuilder();
- private MatchCandidateList mCandidates = new MatchCandidateList();
private ContactMatcher mMatcher = new ContactMatcher();
- private DisplayNameCandidate mDisplayNameCandidate = new DisplayNameCandidate();
-
- /**
- * Parameter for the suggestion lookup query.
- */
- public static final class AggregationSuggestionParameter {
- public final String kind;
- public final String value;
-
- public AggregationSuggestionParameter(String kind, String value) {
- this.kind = kind;
- this.value = value;
- }
- }
-
- /**
- * Captures a potential match for a given name. The matching algorithm
- * constructs a bunch of NameMatchCandidate objects for various potential matches
- * and then executes the search in bulk.
- */
- private static class NameMatchCandidate {
- String mName;
- int mLookupType;
-
- public NameMatchCandidate(String name, int nameLookupType) {
- mName = name;
- mLookupType = nameLookupType;
- }
- }
-
- /**
- * A list of {@link NameMatchCandidate} that keeps its elements even when the list is
- * truncated. This is done for optimization purposes to avoid excessive object allocation.
- */
- private static class MatchCandidateList {
- private final ArrayList<NameMatchCandidate> mList = new ArrayList<NameMatchCandidate>();
- private int mCount;
-
- /**
- * Adds a {@link NameMatchCandidate} element or updates the next one if it already exists.
- */
- public void add(String name, int nameLookupType) {
- if (mCount >= mList.size()) {
- mList.add(new NameMatchCandidate(name, nameLookupType));
- } else {
- NameMatchCandidate candidate = mList.get(mCount);
- candidate.mName = name;
- candidate.mLookupType = nameLookupType;
- }
- mCount++;
- }
-
- public void clear() {
- mCount = 0;
- }
-
- public boolean isEmpty() {
- return mCount == 0;
- }
- }
-
- /**
- * A convenience class used in the algorithm that figures out which of available
- * display names to use for an aggregate contact.
- */
- private static class DisplayNameCandidate {
- long rawContactId;
- String displayName;
- int displayNameSource;
- boolean isNameSuperPrimary;
- boolean writableAccount;
-
- public DisplayNameCandidate() {
- clear();
- }
-
- public void clear() {
- rawContactId = -1;
- displayName = null;
- displayNameSource = DisplayNameSources.UNDEFINED;
- isNameSuperPrimary = false;
- writableAccount = false;
- }
- }
/**
* Constructor.
@@ -284,456 +68,17 @@ public class ContactAggregator {
ContactsDatabaseHelper contactsDatabaseHelper,
PhotoPriorityResolver photoPriorityResolver, NameSplitter nameSplitter,
CommonNicknameCache commonNicknameCache) {
- mContactsProvider = contactsProvider;
- mDbHelper = contactsDatabaseHelper;
- mPhotoPriorityResolver = photoPriorityResolver;
- mNameSplitter = nameSplitter;
- mCommonNicknameCache = commonNicknameCache;
-
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
-
- // Since we have no way of determining which custom status was set last,
- // we'll just pick one randomly. We are using MAX as an approximation of randomness
- final String replaceAggregatePresenceSql =
- "INSERT OR REPLACE INTO " + Tables.AGGREGATED_PRESENCE + "("
- + AggregatedPresenceColumns.CONTACT_ID + ", "
- + StatusUpdates.PRESENCE + ", "
- + StatusUpdates.CHAT_CAPABILITY + ")"
- + " SELECT " + PresenceColumns.CONTACT_ID + ","
- + StatusUpdates.PRESENCE + ","
- + StatusUpdates.CHAT_CAPABILITY
- + " FROM " + Tables.PRESENCE
- + " WHERE "
- + " (" + StatusUpdates.PRESENCE
- + " * 10 + " + StatusUpdates.CHAT_CAPABILITY + ")"
- + " = (SELECT "
- + "MAX (" + StatusUpdates.PRESENCE
- + " * 10 + " + StatusUpdates.CHAT_CAPABILITY + ")"
- + " FROM " + Tables.PRESENCE
- + " WHERE " + PresenceColumns.CONTACT_ID
- + "=?)"
- + " AND " + PresenceColumns.CONTACT_ID
- + "=?;";
- mAggregatedPresenceReplace = db.compileStatement(replaceAggregatePresenceSql);
-
- mRawContactCountQuery = db.compileStatement(
- "SELECT COUNT(" + RawContacts._ID + ")" +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=?"
- + " AND " + RawContacts._ID + "<>?");
-
- mAggregatedPresenceDelete = db.compileStatement(
- "DELETE FROM " + Tables.AGGREGATED_PRESENCE +
- " WHERE " + AggregatedPresenceColumns.CONTACT_ID + "=?");
-
- mMarkForAggregation = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.AGGREGATION_NEEDED + "=1" +
- " WHERE " + RawContacts._ID + "=?"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0");
-
- mPhotoIdUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.PHOTO_ID + "=?," + Contacts.PHOTO_FILE_ID + "=? " +
- " WHERE " + Contacts._ID + "=?");
-
- mDisplayNameUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.NAME_RAW_CONTACT_ID + "=? " +
- " WHERE " + Contacts._ID + "=?");
-
- mLookupKeyUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.LOOKUP_KEY + "=? " +
- " WHERE " + Contacts._ID + "=?");
-
- mStarredUpdate = db.compileStatement("UPDATE " + Tables.CONTACTS + " SET "
- + Contacts.STARRED + "=(SELECT (CASE WHEN COUNT(" + RawContacts.STARRED
- + ")=0 THEN 0 ELSE 1 END) FROM " + Tables.RAW_CONTACTS + " WHERE "
- + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + " AND "
- + RawContacts.STARRED + "=1)" + " WHERE " + Contacts._ID + "=?");
-
- mPinnedUpdate = db.compileStatement("UPDATE " + Tables.CONTACTS + " SET "
- + Contacts.PINNED + " = IFNULL((SELECT MIN(" + RawContacts.PINNED + ") FROM "
- + Tables.RAW_CONTACTS + " WHERE " + RawContacts.CONTACT_ID + "="
- + ContactsColumns.CONCRETE_ID + " AND " + RawContacts.PINNED + ">"
- + PinnedPositions.UNPINNED + ")," + PinnedPositions.UNPINNED + ") "
- + "WHERE " + Contacts._ID + "=?");
-
- mContactIdAndMarkAggregatedUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.CONTACT_ID + "=?, "
- + RawContactsColumns.AGGREGATION_NEEDED + "=0" +
- " WHERE " + RawContacts._ID + "=?");
-
- mContactIdUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.CONTACT_ID + "=?" +
- " WHERE " + RawContacts._ID + "=?");
-
- mMarkAggregatedUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.AGGREGATION_NEEDED + "=0" +
- " WHERE " + RawContacts._ID + "=?");
-
- mPresenceContactIdUpdate = db.compileStatement(
- "UPDATE " + Tables.PRESENCE +
- " SET " + PresenceColumns.CONTACT_ID + "=?" +
- " WHERE " + PresenceColumns.RAW_CONTACT_ID + "=?");
-
- mContactUpdate = db.compileStatement(ContactReplaceSqlStatement.UPDATE_SQL);
- mContactInsert = db.compileStatement(ContactReplaceSqlStatement.INSERT_SQL);
-
- mResetPinnedForRawContact = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.PINNED + "=" + PinnedPositions.UNPINNED +
- " WHERE " + RawContacts._ID + "=?");
-
- mMimeTypeIdEmail = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
- mMimeTypeIdIdentity = mDbHelper.getMimeTypeId(Identity.CONTENT_ITEM_TYPE);
- mMimeTypeIdPhoto = mDbHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
- mMimeTypeIdPhone = mDbHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE);
-
- // Query used to retrieve data from raw contacts to populate the corresponding aggregate
- mRawContactsQueryByRawContactId = String.format(Locale.US,
- RawContactsQuery.SQL_FORMAT_BY_RAW_CONTACT_ID,
- mDbHelper.getMimeTypeIdForStructuredName(), mMimeTypeIdPhoto, mMimeTypeIdPhone);
-
- mRawContactsQueryByContactId = String.format(Locale.US,
- RawContactsQuery.SQL_FORMAT_BY_CONTACT_ID,
- mDbHelper.getMimeTypeIdForStructuredName(), mMimeTypeIdPhoto, mMimeTypeIdPhone);
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- private interface AggregationQuery {
- String SQL =
- "SELECT " + RawContacts._ID + "," + RawContacts.CONTACT_ID +
- ", " + RawContactsColumns.ACCOUNT_ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts._ID + " IN(";
-
- int _ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
+ super(contactsProvider, contactsDatabaseHelper, photoPriorityResolver, nameSplitter,
+ commonNicknameCache);
}
- /**
- * Aggregate all raw contacts that were marked for aggregation in the current transaction.
- * Call just before committing the transaction.
- */
- public void aggregateInTransaction(TransactionContext txContext, SQLiteDatabase db) {
- final int markedCount = mRawContactsMarkedForAggregation.size();
- if (markedCount == 0) {
- return;
- }
-
- final long start = System.currentTimeMillis();
- if (DEBUG_LOGGING) {
- Log.d(TAG, "aggregateInTransaction for " + markedCount + " contacts");
- }
-
- EventLog.writeEvent(LOG_SYNC_CONTACTS_AGGREGATION, start, -markedCount);
-
- int index = 0;
-
- // We don't use the cached string builder (namely mSb) here, as this string can be very
- // long when upgrading (where we re-aggregate all visible contacts) and StringBuilder won't
- // shrink the internal storage.
- // Note: don't use selection args here. We just include all IDs directly in the selection,
- // because there's a limit for the number of parameters in a query.
- final StringBuilder sbQuery = new StringBuilder();
- sbQuery.append(AggregationQuery.SQL);
- for (long rawContactId : mRawContactsMarkedForAggregation.keySet()) {
- if (index > 0) {
- sbQuery.append(',');
- }
- sbQuery.append(rawContactId);
- index++;
- }
-
- sbQuery.append(')');
-
- final long[] rawContactIds;
- final long[] contactIds;
- final long[] accountIds;
- final int actualCount;
- final Cursor c = db.rawQuery(sbQuery.toString(), null);
- try {
- actualCount = c.getCount();
- rawContactIds = new long[actualCount];
- contactIds = new long[actualCount];
- accountIds = new long[actualCount];
-
- index = 0;
- while (c.moveToNext()) {
- rawContactIds[index] = c.getLong(AggregationQuery._ID);
- contactIds[index] = c.getLong(AggregationQuery.CONTACT_ID);
- accountIds[index] = c.getLong(AggregationQuery.ACCOUNT_ID);
- index++;
- }
- } finally {
- c.close();
- }
-
- if (DEBUG_LOGGING) {
- Log.d(TAG, "aggregateInTransaction: initial query done.");
- }
-
- for (int i = 0; i < actualCount; i++) {
- aggregateContact(txContext, db, rawContactIds[i], accountIds[i], contactIds[i],
- mCandidates, mMatcher);
- }
-
- long elapsedTime = System.currentTimeMillis() - start;
- EventLog.writeEvent(LOG_SYNC_CONTACTS_AGGREGATION, elapsedTime, actualCount);
-
- if (DEBUG_LOGGING) {
- Log.d(TAG, "Contact aggregation complete: " + actualCount +
- (actualCount == 0 ? "" : ", " + (elapsedTime / actualCount)
- + " ms per raw contact"));
- }
- }
-
- @SuppressWarnings("deprecation")
- public void triggerAggregation(TransactionContext txContext, long rawContactId) {
- if (!mEnabled) {
- return;
- }
-
- int aggregationMode = mDbHelper.getAggregationMode(rawContactId);
- switch (aggregationMode) {
- case RawContacts.AGGREGATION_MODE_DISABLED:
- break;
-
- case RawContacts.AGGREGATION_MODE_DEFAULT: {
- markForAggregation(rawContactId, aggregationMode, false);
- break;
- }
-
- case RawContacts.AGGREGATION_MODE_SUSPENDED: {
- long contactId = mDbHelper.getContactId(rawContactId);
-
- if (contactId != 0) {
- updateAggregateData(txContext, contactId);
- }
- break;
- }
-
- case RawContacts.AGGREGATION_MODE_IMMEDIATE: {
- aggregateContact(txContext, mDbHelper.getWritableDatabase(), rawContactId);
- break;
- }
- }
- }
-
- public void clearPendingAggregations() {
- // HashMap woulnd't shrink the internal table once expands it, so let's just re-create
- // a new one instead of clear()ing it.
- mRawContactsMarkedForAggregation = Maps.newHashMap();
- }
-
- public void markNewForAggregation(long rawContactId, int aggregationMode) {
- mRawContactsMarkedForAggregation.put(rawContactId, aggregationMode);
- }
-
- public void markForAggregation(long rawContactId, int aggregationMode, boolean force) {
- final int effectiveAggregationMode;
- if (!force && mRawContactsMarkedForAggregation.containsKey(rawContactId)) {
- // As per ContactsContract documentation, default aggregation mode
- // does not override a previously set mode
- if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
- effectiveAggregationMode = mRawContactsMarkedForAggregation.get(rawContactId);
- } else {
- effectiveAggregationMode = aggregationMode;
- }
- } else {
- mMarkForAggregation.bindLong(1, rawContactId);
- mMarkForAggregation.execute();
- effectiveAggregationMode = aggregationMode;
- }
-
- mRawContactsMarkedForAggregation.put(rawContactId, effectiveAggregationMode);
- }
-
- private static class RawContactIdAndAggregationModeQuery {
- public static final String TABLE = Tables.RAW_CONTACTS;
-
- public static final String[] COLUMNS = { RawContacts._ID, RawContacts.AGGREGATION_MODE };
-
- public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
-
- public static final int _ID = 0;
- public static final int AGGREGATION_MODE = 1;
- }
-
- /**
- * Marks all constituent raw contacts of an aggregated contact for re-aggregation.
- */
- private void markContactForAggregation(SQLiteDatabase db, long contactId) {
- mSelectionArgs1[0] = String.valueOf(contactId);
- Cursor cursor = db.query(RawContactIdAndAggregationModeQuery.TABLE,
- RawContactIdAndAggregationModeQuery.COLUMNS,
- RawContactIdAndAggregationModeQuery.SELECTION, mSelectionArgs1, null, null, null);
- try {
- if (cursor.moveToFirst()) {
- long rawContactId = cursor.getLong(RawContactIdAndAggregationModeQuery._ID);
- int aggregationMode = cursor.getInt(
- RawContactIdAndAggregationModeQuery.AGGREGATION_MODE);
- // Don't re-aggregate AGGREGATION_MODE_SUSPENDED / AGGREGATION_MODE_DISABLED.
- // (Also just ignore deprecated AGGREGATION_MODE_IMMEDIATE)
- if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
- markForAggregation(rawContactId, aggregationMode, true);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Mark all visible contacts for re-aggregation.
- *
- * - Set {@link RawContactsColumns#AGGREGATION_NEEDED} For all visible raw_contacts with
- * {@link RawContacts#AGGREGATION_MODE_DEFAULT}.
- * - Also put them into {@link #mRawContactsMarkedForAggregation}.
- */
- public int markAllVisibleForAggregation(SQLiteDatabase db) {
- final long start = System.currentTimeMillis();
-
- // Set AGGREGATION_NEEDED for all visible raw_cotnacts with AGGREGATION_MODE_DEFAULT.
- // (Don't re-aggregate AGGREGATION_MODE_SUSPENDED / AGGREGATION_MODE_DISABLED)
- db.execSQL("UPDATE " + Tables.RAW_CONTACTS + " SET " +
- RawContactsColumns.AGGREGATION_NEEDED + "=1" +
- " WHERE " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY +
- " AND " + RawContacts.AGGREGATION_MODE + "=" + RawContacts.AGGREGATION_MODE_DEFAULT
- );
-
- final int count;
- final Cursor cursor = db.rawQuery("SELECT " + RawContacts._ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContactsColumns.AGGREGATION_NEEDED + "=1", null);
- try {
- count = cursor.getCount();
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- final long rawContactId = cursor.getLong(0);
- mRawContactsMarkedForAggregation.put(rawContactId,
- RawContacts.AGGREGATION_MODE_DEFAULT);
- }
- } finally {
- cursor.close();
- }
-
- final long end = System.currentTimeMillis();
- Log.i(TAG, "Marked all visible contacts for aggregation: " + count + " raw contacts, " +
- (end - start) + " ms");
- return count;
- }
-
- /**
- * Creates a new contact based on the given raw contact. Does not perform aggregation. Returns
- * the ID of the contact that was created.
- */
- public long onRawContactInsert(
- TransactionContext txContext, SQLiteDatabase db, long rawContactId) {
- long contactId = insertContact(db, rawContactId);
- setContactId(rawContactId, contactId);
- mDbHelper.updateContactVisible(txContext, contactId);
- return contactId;
- }
-
- protected long insertContact(SQLiteDatabase db, long rawContactId) {
- mSelectionArgs1[0] = String.valueOf(rawContactId);
- computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1, mContactInsert);
- return mContactInsert.executeInsert();
- }
-
- private static final class RawContactIdAndAccountQuery {
- public static final String TABLE = Tables.RAW_CONTACTS;
-
- public static final String[] COLUMNS = {
- RawContacts.CONTACT_ID,
- RawContactsColumns.ACCOUNT_ID
- };
-
- public static final String SELECTION = RawContacts._ID + "=?";
-
- public static final int CONTACT_ID = 0;
- public static final int ACCOUNT_ID = 1;
- }
-
- public void aggregateContact(
- TransactionContext txContext, SQLiteDatabase db, long rawContactId) {
- if (!mEnabled) {
- return;
- }
-
- MatchCandidateList candidates = new MatchCandidateList();
- ContactMatcher matcher = new ContactMatcher();
-
- long contactId = 0;
- long accountId = 0;
- 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);
- accountId = cursor.getLong(RawContactIdAndAccountQuery.ACCOUNT_ID);
- }
- } finally {
- cursor.close();
- }
-
- aggregateContact(txContext, db, rawContactId, accountId, contactId,
- candidates, matcher);
- }
-
- public void updateAggregateData(TransactionContext txContext, long contactId) {
- if (!mEnabled) {
- return;
- }
-
- final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- computeAggregateData(db, contactId, mContactUpdate);
- mContactUpdate.bindLong(ContactReplaceSqlStatement.CONTACT_ID, contactId);
- mContactUpdate.execute();
-
- mDbHelper.updateContactVisible(txContext, contactId);
- updateAggregatedStatusUpdate(contactId);
- }
-
- private void updateAggregatedStatusUpdate(long contactId) {
- mAggregatedPresenceReplace.bindLong(1, contactId);
- mAggregatedPresenceReplace.bindLong(2, contactId);
- mAggregatedPresenceReplace.execute();
- updateLastStatusUpdateId(contactId);
- }
-
- /**
- * Adjusts the reference to the latest status update for the specified contact.
- */
- public void updateLastStatusUpdateId(long contactId) {
- String contactIdString = String.valueOf(contactId);
- mDbHelper.getWritableDatabase().execSQL(UPDATE_LAST_STATUS_UPDATE_ID_SQL,
- new String[]{contactIdString, contactIdString});
- }
-
- /**
+ /**
* Given a specific raw contact, finds all matching aggregate contacts and chooses the one
* with the highest match score. If no such contact is found, creates a new contact.
*/
- private synchronized void aggregateContact(TransactionContext txContext, SQLiteDatabase db,
- long rawContactId, long accountId, long currentContactId, MatchCandidateList candidates,
- ContactMatcher matcher) {
+ synchronized void aggregateContact(TransactionContext txContext, SQLiteDatabase db,
+ long rawContactId, long accountId, long currentContactId,
+ MatchCandidateList candidates) {
if (VERBOSE_LOGGING) {
Log.v(TAG, "aggregateContact: rid=" + rawContactId + " cid=" + currentContactId);
@@ -749,6 +94,7 @@ public class ContactAggregator {
long contactId = -1; // Best matching contact ID.
boolean needReaggregate = false;
+ final ContactMatcher matcher = new ContactMatcher();
final Set<Long> rawContactIdsInSameAccount = new HashSet<Long>();
final Set<Long> rawContactIdsInOtherAccount = new HashSet<Long>();
if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
@@ -1017,87 +363,6 @@ public class ContactAggregator {
}
}
- private interface RawContactMatchingSelectionStatement {
- String SELECT_COUNT = "SELECT count(*) " ;
- String SELECT_ID = "SELECT d1." + Data.RAW_CONTACT_ID + ",d2." + Data.RAW_CONTACT_ID ;
- }
-
- /**
- * Build sql to check if there is any identity match/mis-match between two sets of raw contact
- * ids on the same namespace.
- */
- private String buildIdentityMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
- boolean isIdentityMatching, boolean countOnly) {
- final String identityType = String.valueOf(mMimeTypeIdIdentity);
- final String matchingOperator = (isIdentityMatching) ? "=" : "!=";
- final String sql =
- " FROM " + Tables.DATA + " AS d1" +
- " JOIN " + Tables.DATA + " AS d2" +
- " ON (d1." + Identity.IDENTITY + matchingOperator +
- " d2." + Identity.IDENTITY + " AND" +
- " d1." + Identity.NAMESPACE + " = d2." + Identity.NAMESPACE + " )" +
- " WHERE d1." + DataColumns.MIMETYPE_ID + " = " + identityType +
- " AND d2." + DataColumns.MIMETYPE_ID + " = " + identityType +
- " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
- " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")";
- return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
- RawContactMatchingSelectionStatement.SELECT_ID + sql;
- }
-
- private String buildEmailMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
- boolean countOnly) {
- final String emailType = String.valueOf(mMimeTypeIdEmail);
- final String sql =
- " FROM " + Tables.DATA + " AS d1" +
- " JOIN " + Tables.DATA + " AS d2" +
- " ON lower(d1." + Email.ADDRESS + ")= lower(d2." + Email.ADDRESS + ")" +
- " WHERE d1." + DataColumns.MIMETYPE_ID + " = " + emailType +
- " AND d2." + DataColumns.MIMETYPE_ID + " = " + emailType +
- " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
- " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")";
- return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
- RawContactMatchingSelectionStatement.SELECT_ID + sql;
- }
-
- private String buildPhoneMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
- boolean countOnly) {
- // It's a bit tricker because it has to be consistent with
- // updateMatchScoresBasedOnPhoneMatches().
- final String phoneType = String.valueOf(mMimeTypeIdPhone);
- final String sql =
- " FROM " + Tables.PHONE_LOOKUP + " AS p1" +
- " JOIN " + Tables.DATA + " AS d1 ON " +
- "(d1." + Data._ID + "=p1." + PhoneLookupColumns.DATA_ID + ")" +
- " JOIN " + Tables.PHONE_LOOKUP + " AS p2 ON (p1." + PhoneLookupColumns.MIN_MATCH +
- "=p2." + PhoneLookupColumns.MIN_MATCH + ")" +
- " JOIN " + Tables.DATA + " AS d2 ON " +
- "(d2." + Data._ID + "=p2." + PhoneLookupColumns.DATA_ID + ")" +
- " WHERE d1." + DataColumns.MIMETYPE_ID + " = " + phoneType +
- " AND d2." + DataColumns.MIMETYPE_ID + " = " + phoneType +
- " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
- " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")" +
- " AND PHONE_NUMBERS_EQUAL(d1." + Phone.NUMBER + ",d2." + Phone.NUMBER + "," +
- String.valueOf(mDbHelper.getUseStrictPhoneNumberComparisonParameter()) +
- ")";
- return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
- RawContactMatchingSelectionStatement.SELECT_ID + sql;
- }
-
- private String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2) {
- return "SELECT " + AggregationExceptions.RAW_CONTACT_ID1 + ", " +
- AggregationExceptions.RAW_CONTACT_ID2 +
- " FROM " + Tables.AGGREGATION_EXCEPTIONS +
- " WHERE " + AggregationExceptions.RAW_CONTACT_ID1 + " IN (" +
- rawContactIdSet1 + ")" +
- " AND " + AggregationExceptions.RAW_CONTACT_ID2 + " IN (" + rawContactIdSet2 + ")" +
- " AND " + AggregationExceptions.TYPE + "=" +
- AggregationExceptions.TYPE_KEEP_TOGETHER ;
- }
-
- private boolean isFirstColumnGreaterThanZero(SQLiteDatabase db, String query) {
- return DatabaseUtils.longForQuery(db, query, null) > 0;
- }
-
/**
* If there's any identity, email address or a phone number matching between two raw contact
* sets.
@@ -1191,82 +456,6 @@ public class ContactAggregator {
}
/**
- * Partition the given raw contact Ids to connected component based on aggregation exception,
- * identity matching, email matching or phone matching.
- */
- private Set<Set<Long>> findConnectedRawContacts(SQLiteDatabase db, Set<Long> rawContactIdSet) {
- // Connections between two raw contacts
- final Multimap<Long, Long> matchingRawIdPairs = HashMultimap.create();
- String rawContactIds = TextUtils.join(",", rawContactIdSet);
- findIdPairs(db, buildExceptionMatchingSql(rawContactIds, rawContactIds),
- matchingRawIdPairs);
- findIdPairs(db, buildIdentityMatchingSql(rawContactIds, rawContactIds,
- /* isIdentityMatching =*/ true, /* countOnly =*/false), matchingRawIdPairs);
- findIdPairs(db, buildEmailMatchingSql(rawContactIds, rawContactIds, /* countOnly =*/false),
- matchingRawIdPairs);
- findIdPairs(db, buildPhoneMatchingSql(rawContactIds, rawContactIds, /* countOnly =*/false),
- matchingRawIdPairs);
-
- return ContactAggregatorHelper.findConnectedComponents(rawContactIdSet, matchingRawIdPairs);
- }
-
- /**
- * Given a query which will return two non-null IDs in the first two columns as results, this
- * method will put two entries into the given result map for each pair of different IDs, one
- * keyed by each ID.
- */
- private void findIdPairs(SQLiteDatabase db, String query, Multimap<Long, Long> results) {
- Cursor cursor = db.rawQuery(query, null);
- try {
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- long idA = cursor.getLong(0);
- long idB = cursor.getLong(1);
- if (idA != idB) {
- results.put(idA, idB);
- results.put(idB, idA);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Creates a new Contact for a given set of the raw contacts of {@code rawContactIds} if the
- * given contactId is null. Otherwise, regroup them into contact with {@code contactId}.
- */
- private void createContactForRawContacts(SQLiteDatabase db, TransactionContext txContext,
- Set<Long> rawContactIds, Long contactId) {
- if (rawContactIds.isEmpty()) {
- // No raw contact id is provided.
- return;
- }
-
- // If contactId is not provided, generates a new one.
- if (contactId == null) {
- mSelectionArgs1[0]= String.valueOf(rawContactIds.iterator().next());
- computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1,
- mContactInsert);
- contactId = mContactInsert.executeInsert();
- }
- for (Long rawContactId : rawContactIds) {
- // Regrouped contacts should automatically be unpinned.
- unpinRawContact(rawContactId);
- setContactIdAndMarkAggregated(rawContactId, contactId);
- setPresenceContactId(rawContactId, contactId);
- }
- updateAggregateData(txContext, contactId);
- }
-
- private static class RawContactIdQuery {
- public static final String TABLE = Tables.RAW_CONTACTS;
- public static final String[] COLUMNS = { RawContacts._ID };
- public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
- public static final int RAW_CONTACT_ID = 0;
- }
-
- /**
* Ensures that automatic aggregation rules are followed after a contact
* becomes visible or invisible. Specifically, consider this case: there are
* three contacts named Foo. Two of them come from account A1 and one comes
@@ -1331,14 +520,6 @@ public class ContactAggregator {
}
/**
- * Marks the specified raw contact ID as aggregated
- */
- private void markAggregated(long rawContactId) {
- mMarkAggregatedUpdate.bindLong(1, rawContactId);
- mMarkAggregatedUpdate.execute();
- }
-
- /**
* Updates the contact ID for the specified contact and marks the raw contact as aggregated.
*/
private void setContactIdAndMarkAggregated(long rawContactId, long contactId) {
@@ -1347,63 +528,14 @@ public class ContactAggregator {
mContactIdAndMarkAggregatedUpdate.execute();
}
- private void setPresenceContactId(long rawContactId, long contactId) {
- mPresenceContactIdUpdate.bindLong(1, contactId);
- mPresenceContactIdUpdate.bindLong(2, rawContactId);
- mPresenceContactIdUpdate.execute();
- }
-
- private void unpinRawContact(long rawContactId) {
- mResetPinnedForRawContact.bindLong(1, rawContactId);
- mResetPinnedForRawContact.execute();
- }
-
- interface AggregateExceptionPrefetchQuery {
- String TABLE = Tables.AGGREGATION_EXCEPTIONS;
-
- String[] COLUMNS = {
- AggregationExceptions.RAW_CONTACT_ID1,
- AggregationExceptions.RAW_CONTACT_ID2,
- };
-
- int RAW_CONTACT_ID1 = 0;
- int RAW_CONTACT_ID2 = 1;
- }
// A set of raw contact IDs for which there are aggregation exceptions
- private final HashSet<Long> mAggregationExceptionIds = new HashSet<Long>();
private boolean mAggregationExceptionIdsValid;
public void invalidateAggregationExceptionCache() {
mAggregationExceptionIdsValid = false;
}
- /**
- * Finds all raw contact IDs for which there are aggregation exceptions. The list of
- * ids is used as an optimization in aggregation: there is no point to run a query against
- * the agg_exceptions table if it is known that there are no records there for a given
- * raw contact ID.
- */
- private void prefetchAggregationExceptionIds(SQLiteDatabase db) {
- mAggregationExceptionIds.clear();
- final Cursor c = db.query(AggregateExceptionPrefetchQuery.TABLE,
- AggregateExceptionPrefetchQuery.COLUMNS,
- null, null, null, null, null);
-
- try {
- while (c.moveToNext()) {
- long rawContactId1 = c.getLong(AggregateExceptionPrefetchQuery.RAW_CONTACT_ID1);
- long rawContactId2 = c.getLong(AggregateExceptionPrefetchQuery.RAW_CONTACT_ID2);
- mAggregationExceptionIds.add(rawContactId1);
- mAggregationExceptionIds.add(rawContactId2);
- }
- } finally {
- c.close();
- }
-
- mAggregationExceptionIdsValid = true;
- }
-
interface AggregateExceptionQuery {
String TABLE = Tables.AGGREGATION_EXCEPTIONS
+ " JOIN raw_contacts raw_contacts1 "
@@ -1478,7 +610,7 @@ public class ContactAggregator {
c.close();
}
- return matcher.pickBestMatch(ContactMatcher.MAX_SCORE, true);
+ return matcher.pickBestMatch(MatchScore.MAX_SCORE, true);
}
/**
@@ -1549,42 +681,6 @@ public class ContactAggregator {
return matcher.pickBestMatch(ContactMatcher.SCORE_THRESHOLD_SECONDARY, false);
}
- private interface NameLookupQuery {
- String TABLE = Tables.NAME_LOOKUP;
-
- String SELECTION = NameLookupColumns.RAW_CONTACT_ID + "=?";
- String SELECTION_STRUCTURED_NAME_BASED =
- SELECTION + " AND " + STRUCTURED_NAME_BASED_LOOKUP_SQL;
-
- String[] COLUMNS = new String[] {
- NameLookupColumns.NORMALIZED_NAME,
- NameLookupColumns.NAME_TYPE
- };
-
- int NORMALIZED_NAME = 0;
- int NAME_TYPE = 1;
- }
-
- private void loadNameMatchCandidates(SQLiteDatabase db, long rawContactId,
- MatchCandidateList candidates, boolean structuredNameBased) {
- candidates.clear();
- mSelectionArgs1[0] = String.valueOf(rawContactId);
- Cursor c = db.query(NameLookupQuery.TABLE, NameLookupQuery.COLUMNS,
- structuredNameBased
- ? NameLookupQuery.SELECTION_STRUCTURED_NAME_BASED
- : NameLookupQuery.SELECTION,
- mSelectionArgs1, null, null, null);
- try {
- while (c.moveToNext()) {
- String normalizedName = c.getString(NameLookupQuery.NORMALIZED_NAME);
- int type = c.getInt(NameLookupQuery.NAME_TYPE);
- candidates.add(normalizedName, type);
- }
- } finally {
- c.close();
- }
- }
-
/**
* Computes scores for contacts that have matching data rows.
*/
@@ -1702,124 +798,6 @@ public class ContactAggregator {
}
}
- private interface NameLookupMatchQueryWithParameter {
- String TABLE = Tables.NAME_LOOKUP
- + " JOIN " + Tables.RAW_CONTACTS +
- " ON (" + NameLookupColumns.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID,
- NameLookupColumns.NORMALIZED_NAME,
- NameLookupColumns.NAME_TYPE,
- };
-
- int CONTACT_ID = 0;
- int NAME = 1;
- int NAME_TYPE = 2;
- }
-
- private final class NameLookupSelectionBuilder extends NameLookupBuilder {
-
- private final MatchCandidateList mNameLookupCandidates;
-
- private StringBuilder mSelection = new StringBuilder(
- NameLookupColumns.NORMALIZED_NAME + " IN(");
-
-
- public NameLookupSelectionBuilder(NameSplitter splitter, MatchCandidateList candidates) {
- super(splitter);
- this.mNameLookupCandidates = candidates;
- }
-
- @Override
- protected String[] getCommonNicknameClusters(String normalizedName) {
- return mCommonNicknameCache.getCommonNicknameClusters(normalizedName);
- }
-
- @Override
- protected void insertNameLookup(
- long rawContactId, long dataId, int lookupType, String string) {
- mNameLookupCandidates.add(string, lookupType);
- DatabaseUtils.appendEscapedSQLString(mSelection, string);
- mSelection.append(',');
- }
-
- public boolean isEmpty() {
- return mNameLookupCandidates.isEmpty();
- }
-
- public String getSelection() {
- mSelection.setLength(mSelection.length() - 1); // Strip last comma
- mSelection.append(')');
- return mSelection.toString();
- }
-
- public int getLookupType(String name) {
- for (int i = 0; i < mNameLookupCandidates.mCount; i++) {
- if (mNameLookupCandidates.mList.get(i).mName.equals(name)) {
- return mNameLookupCandidates.mList.get(i).mLookupType;
- }
- }
- throw new IllegalStateException();
- }
- }
-
- /**
- * Finds contacts with names matching the specified name.
- */
- private void updateMatchScoresBasedOnNameMatches(SQLiteDatabase db, String query,
- MatchCandidateList candidates, ContactMatcher matcher) {
- candidates.clear();
- NameLookupSelectionBuilder builder = new NameLookupSelectionBuilder(
- mNameSplitter, candidates);
- builder.insertNameLookup(0, 0, query, FullNameStyle.UNDEFINED);
- if (builder.isEmpty()) {
- return;
- }
-
- Cursor c = db.query(NameLookupMatchQueryWithParameter.TABLE,
- NameLookupMatchQueryWithParameter.COLUMNS, builder.getSelection(), null, null, null,
- null, PRIMARY_HIT_LIMIT_STRING);
- try {
- while (c.moveToNext()) {
- long contactId = c.getLong(NameLookupMatchQueryWithParameter.CONTACT_ID);
- String name = c.getString(NameLookupMatchQueryWithParameter.NAME);
- int nameTypeA = builder.getLookupType(name);
- int nameTypeB = c.getInt(NameLookupMatchQueryWithParameter.NAME_TYPE);
- matcher.matchName(contactId, nameTypeA, name, nameTypeB, name,
- ContactMatcher.MATCHING_ALGORITHM_EXACT);
- if (nameTypeA == NameLookupType.NICKNAME && nameTypeB == NameLookupType.NICKNAME) {
- matcher.updateScoreWithNicknameMatch(contactId);
- }
- }
- } finally {
- c.close();
- }
- }
-
- private interface EmailLookupQuery {
- String TABLE = Tables.DATA + " dataA"
- + " JOIN " + Tables.DATA + " dataB" +
- " ON lower(" + "dataA." + Email.DATA + ")=lower(dataB." + Email.DATA + ")"
- + " JOIN " + Tables.RAW_CONTACTS +
- " ON (dataB." + Data.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String SELECTION = "dataA." + Data.RAW_CONTACT_ID + "=?1"
- + " AND dataA." + DataColumns.MIMETYPE_ID + "=?2"
- + " AND dataA." + Email.DATA + " NOT NULL"
- + " AND dataB." + DataColumns.MIMETYPE_ID + "=?2"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
- + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
-
- String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID
- };
-
- int CONTACT_ID = 0;
- }
-
private void updateMatchScoresBasedOnEmailMatches(SQLiteDatabase db, long rawContactId,
ContactMatcher matcher) {
mSelectionArgs2[0] = String.valueOf(rawContactId);
@@ -1837,32 +815,6 @@ public class ContactAggregator {
}
}
- private interface PhoneLookupQuery {
- String TABLE = Tables.PHONE_LOOKUP + " phoneA"
- + " JOIN " + Tables.DATA + " dataA"
- + " ON (dataA." + Data._ID + "=phoneA." + PhoneLookupColumns.DATA_ID + ")"
- + " JOIN " + Tables.PHONE_LOOKUP + " phoneB"
- + " ON (phoneA." + PhoneLookupColumns.MIN_MATCH + "="
- + "phoneB." + PhoneLookupColumns.MIN_MATCH + ")"
- + " JOIN " + Tables.DATA + " dataB"
- + " ON (dataB." + Data._ID + "=phoneB." + PhoneLookupColumns.DATA_ID + ")"
- + " JOIN " + Tables.RAW_CONTACTS
- + " ON (dataB." + Data.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String SELECTION = "dataA." + Data.RAW_CONTACT_ID + "=?"
- + " AND PHONE_NUMBERS_EQUAL(dataA." + Phone.NUMBER + ", "
- + "dataB." + Phone.NUMBER + ",?)"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
- + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
-
- String[] COLUMNS = new String[] {
- RawContacts.CONTACT_ID
- };
-
- int CONTACT_ID = 0;
- }
-
private void updateMatchScoresBasedOnPhoneMatches(SQLiteDatabase db, long rawContactId,
ContactMatcher matcher) {
mSelectionArgs2[0] = String.valueOf(rawContactId);
@@ -1951,804 +903,12 @@ public class ContactAggregator {
}
}
- private interface RawContactsQuery {
- String SQL_FORMAT_HAS_SUPER_PRIMARY_NAME =
- " EXISTS(SELECT 1 " +
- " FROM " + Tables.DATA + " d " +
- " WHERE d." + DataColumns.MIMETYPE_ID + "=%d " +
- " AND d." + Data.RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID +
- " AND d." + Data.IS_SUPER_PRIMARY + "=1)";
-
- String SQL_FORMAT =
- "SELECT "
- + RawContactsColumns.CONCRETE_ID + ","
- + RawContactsColumns.DISPLAY_NAME + ","
- + RawContactsColumns.DISPLAY_NAME_SOURCE + ","
- + AccountsColumns.CONCRETE_ACCOUNT_TYPE + ","
- + AccountsColumns.CONCRETE_ACCOUNT_NAME + ","
- + AccountsColumns.CONCRETE_DATA_SET + ","
- + RawContacts.SOURCE_ID + ","
- + RawContacts.CUSTOM_RINGTONE + ","
- + RawContacts.SEND_TO_VOICEMAIL + ","
- + RawContacts.LAST_TIME_CONTACTED + ","
- + RawContacts.TIMES_CONTACTED + ","
- + RawContacts.STARRED + ","
- + RawContacts.PINNED + ","
- + DataColumns.CONCRETE_ID + ","
- + DataColumns.CONCRETE_MIMETYPE_ID + ","
- + Data.IS_SUPER_PRIMARY + ","
- + Photo.PHOTO_FILE_ID + ","
- + SQL_FORMAT_HAS_SUPER_PRIMARY_NAME +
- " FROM " + Tables.RAW_CONTACTS +
- " JOIN " + Tables.ACCOUNTS + " ON ("
- + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
- + ")" +
- " LEFT OUTER JOIN " + Tables.DATA +
- " ON (" + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
- + " AND ((" + DataColumns.MIMETYPE_ID + "=%d"
- + " AND " + Photo.PHOTO + " NOT NULL)"
- + " OR (" + DataColumns.MIMETYPE_ID + "=%d"
- + " AND " + Phone.NUMBER + " NOT NULL)))";
-
- String SQL_FORMAT_BY_RAW_CONTACT_ID = SQL_FORMAT +
- " WHERE " + RawContactsColumns.CONCRETE_ID + "=?";
-
- String SQL_FORMAT_BY_CONTACT_ID = SQL_FORMAT +
- " WHERE " + RawContacts.CONTACT_ID + "=?"
- + " AND " + RawContacts.DELETED + "=0";
-
- int RAW_CONTACT_ID = 0;
- int DISPLAY_NAME = 1;
- int DISPLAY_NAME_SOURCE = 2;
- int ACCOUNT_TYPE = 3;
- int ACCOUNT_NAME = 4;
- int DATA_SET = 5;
- int SOURCE_ID = 6;
- int CUSTOM_RINGTONE = 7;
- int SEND_TO_VOICEMAIL = 8;
- int LAST_TIME_CONTACTED = 9;
- int TIMES_CONTACTED = 10;
- int STARRED = 11;
- int PINNED = 12;
- int DATA_ID = 13;
- int MIMETYPE_ID = 14;
- int IS_SUPER_PRIMARY = 15;
- int PHOTO_FILE_ID = 16;
- int HAS_SUPER_PRIMARY_NAME = 17;
- }
-
- private interface ContactReplaceSqlStatement {
- String UPDATE_SQL =
- "UPDATE " + Tables.CONTACTS +
- " SET "
- + Contacts.NAME_RAW_CONTACT_ID + "=?, "
- + Contacts.PHOTO_ID + "=?, "
- + Contacts.PHOTO_FILE_ID + "=?, "
- + Contacts.SEND_TO_VOICEMAIL + "=?, "
- + Contacts.CUSTOM_RINGTONE + "=?, "
- + Contacts.LAST_TIME_CONTACTED + "=?, "
- + Contacts.TIMES_CONTACTED + "=?, "
- + Contacts.STARRED + "=?, "
- + Contacts.PINNED + "=?, "
- + Contacts.HAS_PHONE_NUMBER + "=?, "
- + Contacts.LOOKUP_KEY + "=?, "
- + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + "=? " +
- " WHERE " + Contacts._ID + "=?";
-
- String INSERT_SQL =
- "INSERT INTO " + Tables.CONTACTS + " ("
- + Contacts.NAME_RAW_CONTACT_ID + ", "
- + Contacts.PHOTO_ID + ", "
- + Contacts.PHOTO_FILE_ID + ", "
- + Contacts.SEND_TO_VOICEMAIL + ", "
- + Contacts.CUSTOM_RINGTONE + ", "
- + Contacts.LAST_TIME_CONTACTED + ", "
- + Contacts.TIMES_CONTACTED + ", "
- + Contacts.STARRED + ", "
- + Contacts.PINNED + ", "
- + Contacts.HAS_PHONE_NUMBER + ", "
- + Contacts.LOOKUP_KEY + ", "
- + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
- + ") " +
- " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";
-
- int NAME_RAW_CONTACT_ID = 1;
- int PHOTO_ID = 2;
- int PHOTO_FILE_ID = 3;
- int SEND_TO_VOICEMAIL = 4;
- int CUSTOM_RINGTONE = 5;
- int LAST_TIME_CONTACTED = 6;
- int TIMES_CONTACTED = 7;
- int STARRED = 8;
- int PINNED = 9;
- int HAS_PHONE_NUMBER = 10;
- int LOOKUP_KEY = 11;
- int CONTACT_LAST_UPDATED_TIMESTAMP = 12;
- int CONTACT_ID = 13;
- }
-
- /**
- * Computes aggregate-level data for the specified aggregate contact ID.
- */
- private void computeAggregateData(SQLiteDatabase db, long contactId,
- SQLiteStatement statement) {
- mSelectionArgs1[0] = String.valueOf(contactId);
- computeAggregateData(db, mRawContactsQueryByContactId, mSelectionArgs1, statement);
- }
-
- /**
- * Indicates whether the given photo entry and priority gives this photo a higher overall
- * priority than the current best photo entry and priority.
- */
- private boolean hasHigherPhotoPriority(PhotoEntry photoEntry, int priority,
- PhotoEntry bestPhotoEntry, int bestPriority) {
- int photoComparison = photoEntry.compareTo(bestPhotoEntry);
- return photoComparison < 0 || photoComparison == 0 && priority > bestPriority;
- }
-
- /**
- * Computes aggregate-level data from constituent raw contacts.
- */
- private void computeAggregateData(final SQLiteDatabase db, String sql, String[] sqlArgs,
- SQLiteStatement statement) {
- long currentRawContactId = -1;
- long bestPhotoId = -1;
- long bestPhotoFileId = 0;
- PhotoEntry bestPhotoEntry = null;
- boolean foundSuperPrimaryPhoto = false;
- int photoPriority = -1;
- int totalRowCount = 0;
- int contactSendToVoicemail = 0;
- String contactCustomRingtone = null;
- long contactLastTimeContacted = 0;
- int contactTimesContacted = 0;
- int contactStarred = 0;
- int contactPinned = Integer.MAX_VALUE;
- int hasPhoneNumber = 0;
- StringBuilder lookupKey = new StringBuilder();
-
- mDisplayNameCandidate.clear();
-
- Cursor c = db.rawQuery(sql, sqlArgs);
- try {
- while (c.moveToNext()) {
- long rawContactId = c.getLong(RawContactsQuery.RAW_CONTACT_ID);
- if (rawContactId != currentRawContactId) {
- currentRawContactId = rawContactId;
- totalRowCount++;
-
- // Assemble sub-account.
- String accountType = c.getString(RawContactsQuery.ACCOUNT_TYPE);
- String dataSet = c.getString(RawContactsQuery.DATA_SET);
- String accountWithDataSet = (!TextUtils.isEmpty(dataSet))
- ? accountType + "/" + dataSet
- : accountType;
-
- // Display name
- String displayName = c.getString(RawContactsQuery.DISPLAY_NAME);
- int displayNameSource = c.getInt(RawContactsQuery.DISPLAY_NAME_SOURCE);
- int isNameSuperPrimary = c.getInt(RawContactsQuery.HAS_SUPER_PRIMARY_NAME);
- processDisplayNameCandidate(rawContactId, displayName, displayNameSource,
- mContactsProvider.isWritableAccountWithDataSet(accountWithDataSet),
- isNameSuperPrimary != 0);
-
- // Contact options
- if (!c.isNull(RawContactsQuery.SEND_TO_VOICEMAIL)) {
- boolean sendToVoicemail =
- (c.getInt(RawContactsQuery.SEND_TO_VOICEMAIL) != 0);
- if (sendToVoicemail) {
- contactSendToVoicemail++;
- }
- }
-
- if (contactCustomRingtone == null
- && !c.isNull(RawContactsQuery.CUSTOM_RINGTONE)) {
- contactCustomRingtone = c.getString(RawContactsQuery.CUSTOM_RINGTONE);
- }
-
- long lastTimeContacted = c.getLong(RawContactsQuery.LAST_TIME_CONTACTED);
- if (lastTimeContacted > contactLastTimeContacted) {
- contactLastTimeContacted = lastTimeContacted;
- }
-
- int timesContacted = c.getInt(RawContactsQuery.TIMES_CONTACTED);
- if (timesContacted > contactTimesContacted) {
- contactTimesContacted = timesContacted;
- }
-
- if (c.getInt(RawContactsQuery.STARRED) != 0) {
- contactStarred = 1;
- }
-
- // contactPinned should be the lowest value of its constituent raw contacts,
- // excluding negative integers
- final int rawContactPinned = c.getInt(RawContactsQuery.PINNED);
- if (rawContactPinned > PinnedPositions.UNPINNED) {
- contactPinned = Math.min(contactPinned, rawContactPinned);
- }
-
- appendLookupKey(
- lookupKey,
- accountWithDataSet,
- c.getString(RawContactsQuery.ACCOUNT_NAME),
- rawContactId,
- c.getString(RawContactsQuery.SOURCE_ID),
- displayName);
- }
-
- if (!c.isNull(RawContactsQuery.DATA_ID)) {
- long dataId = c.getLong(RawContactsQuery.DATA_ID);
- long photoFileId = c.getLong(RawContactsQuery.PHOTO_FILE_ID);
- int mimetypeId = c.getInt(RawContactsQuery.MIMETYPE_ID);
- boolean superPrimary = c.getInt(RawContactsQuery.IS_SUPER_PRIMARY) != 0;
- if (mimetypeId == mMimeTypeIdPhoto) {
- if (!foundSuperPrimaryPhoto) {
- // Lookup the metadata for the photo, if available. Note that data set
- // does not come into play here, since accounts are looked up in the
- // account manager in the priority resolver.
- PhotoEntry photoEntry = getPhotoMetadata(db, photoFileId);
- String accountType = c.getString(RawContactsQuery.ACCOUNT_TYPE);
- int priority = mPhotoPriorityResolver.getPhotoPriority(accountType);
- if (superPrimary || hasHigherPhotoPriority(
- photoEntry, priority, bestPhotoEntry, photoPriority)) {
- bestPhotoEntry = photoEntry;
- photoPriority = priority;
- bestPhotoId = dataId;
- bestPhotoFileId = photoFileId;
- foundSuperPrimaryPhoto |= superPrimary;
- }
- }
- } else if (mimetypeId == mMimeTypeIdPhone) {
- hasPhoneNumber = 1;
- }
- }
- }
- } finally {
- c.close();
- }
-
- if (contactPinned == Integer.MAX_VALUE) {
- contactPinned = PinnedPositions.UNPINNED;
- }
-
- statement.bindLong(ContactReplaceSqlStatement.NAME_RAW_CONTACT_ID,
- mDisplayNameCandidate.rawContactId);
-
- if (bestPhotoId != -1) {
- statement.bindLong(ContactReplaceSqlStatement.PHOTO_ID, bestPhotoId);
- } else {
- statement.bindNull(ContactReplaceSqlStatement.PHOTO_ID);
- }
-
- if (bestPhotoFileId != 0) {
- statement.bindLong(ContactReplaceSqlStatement.PHOTO_FILE_ID, bestPhotoFileId);
- } else {
- statement.bindNull(ContactReplaceSqlStatement.PHOTO_FILE_ID);
- }
-
- statement.bindLong(ContactReplaceSqlStatement.SEND_TO_VOICEMAIL,
- totalRowCount == contactSendToVoicemail ? 1 : 0);
- DatabaseUtils.bindObjectToProgram(statement, ContactReplaceSqlStatement.CUSTOM_RINGTONE,
- contactCustomRingtone);
- statement.bindLong(ContactReplaceSqlStatement.LAST_TIME_CONTACTED,
- contactLastTimeContacted);
- statement.bindLong(ContactReplaceSqlStatement.TIMES_CONTACTED,
- contactTimesContacted);
- statement.bindLong(ContactReplaceSqlStatement.STARRED,
- contactStarred);
- statement.bindLong(ContactReplaceSqlStatement.PINNED,
- contactPinned);
- statement.bindLong(ContactReplaceSqlStatement.HAS_PHONE_NUMBER,
- hasPhoneNumber);
- statement.bindString(ContactReplaceSqlStatement.LOOKUP_KEY,
- Uri.encode(lookupKey.toString()));
- statement.bindLong(ContactReplaceSqlStatement.CONTACT_LAST_UPDATED_TIMESTAMP,
- Clock.getInstance().currentTimeMillis());
- }
-
- /**
- * Builds a lookup key using the given data.
- */
- protected void appendLookupKey(StringBuilder sb, String accountTypeWithDataSet,
- String accountName, long rawContactId, String sourceId, String displayName) {
- ContactLookupKey.appendToLookupKey(sb, accountTypeWithDataSet, accountName, rawContactId,
- sourceId, displayName);
- }
-
- /**
- * Uses the supplied values to determine if they represent a "better" display name
- * for the aggregate contact currently evaluated. If so, it updates
- * {@link #mDisplayNameCandidate} with the new values.
- */
- private void processDisplayNameCandidate(long rawContactId, String displayName,
- int displayNameSource, boolean writableAccount, boolean isNameSuperPrimary) {
-
- boolean replace = false;
- if (mDisplayNameCandidate.rawContactId == -1) {
- // No previous values available
- replace = true;
- } else if (!TextUtils.isEmpty(displayName)) {
- if (isNameSuperPrimary) {
- // A super primary name is better than any other name
- replace = true;
- } else if (mDisplayNameCandidate.isNameSuperPrimary == isNameSuperPrimary) {
- if (mDisplayNameCandidate.displayNameSource < displayNameSource) {
- // New values come from an superior source, e.g. structured name vs phone number
- replace = true;
- } else if (mDisplayNameCandidate.displayNameSource == displayNameSource) {
- if (!mDisplayNameCandidate.writableAccount && writableAccount) {
- replace = true;
- } else if (mDisplayNameCandidate.writableAccount == writableAccount) {
- if (NameNormalizer.compareComplexity(displayName,
- mDisplayNameCandidate.displayName) > 0) {
- // New name is more complex than the previously found one
- replace = true;
- }
- }
- }
- }
- }
-
- if (replace) {
- mDisplayNameCandidate.rawContactId = rawContactId;
- mDisplayNameCandidate.displayName = displayName;
- mDisplayNameCandidate.displayNameSource = displayNameSource;
- mDisplayNameCandidate.isNameSuperPrimary = isNameSuperPrimary;
- mDisplayNameCandidate.writableAccount = writableAccount;
- }
- }
-
- private interface PhotoIdQuery {
- final String[] COLUMNS = new String[] {
- AccountsColumns.CONCRETE_ACCOUNT_TYPE,
- DataColumns.CONCRETE_ID,
- Data.IS_SUPER_PRIMARY,
- Photo.PHOTO_FILE_ID,
- };
-
- int ACCOUNT_TYPE = 0;
- int DATA_ID = 1;
- int IS_SUPER_PRIMARY = 2;
- int PHOTO_FILE_ID = 3;
- }
-
- public void updatePhotoId(SQLiteDatabase db, long rawContactId) {
-
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- long bestPhotoId = -1;
- long bestPhotoFileId = 0;
- int photoPriority = -1;
-
- long photoMimeType = mDbHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
-
- String tables = Tables.RAW_CONTACTS
- + " JOIN " + Tables.ACCOUNTS + " ON ("
- + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
- + ")"
- + " JOIN " + Tables.DATA + " ON("
- + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
- + " AND (" + DataColumns.MIMETYPE_ID + "=" + photoMimeType + " AND "
- + Photo.PHOTO + " NOT NULL))";
-
- mSelectionArgs1[0] = String.valueOf(contactId);
- final Cursor c = db.query(tables, PhotoIdQuery.COLUMNS,
- RawContacts.CONTACT_ID + "=?", mSelectionArgs1, null, null, null);
- try {
- PhotoEntry bestPhotoEntry = null;
- while (c.moveToNext()) {
- long dataId = c.getLong(PhotoIdQuery.DATA_ID);
- long photoFileId = c.getLong(PhotoIdQuery.PHOTO_FILE_ID);
- boolean superPrimary = c.getInt(PhotoIdQuery.IS_SUPER_PRIMARY) != 0;
- PhotoEntry photoEntry = getPhotoMetadata(db, photoFileId);
-
- // Note that data set does not come into play here, since accounts are looked up in
- // the account manager in the priority resolver.
- String accountType = c.getString(PhotoIdQuery.ACCOUNT_TYPE);
- int priority = mPhotoPriorityResolver.getPhotoPriority(accountType);
- if (superPrimary || hasHigherPhotoPriority(
- photoEntry, priority, bestPhotoEntry, photoPriority)) {
- bestPhotoEntry = photoEntry;
- photoPriority = priority;
- bestPhotoId = dataId;
- bestPhotoFileId = photoFileId;
- if (superPrimary) {
- break;
- }
- }
- }
- } finally {
- c.close();
- }
-
- if (bestPhotoId == -1) {
- mPhotoIdUpdate.bindNull(1);
- } else {
- mPhotoIdUpdate.bindLong(1, bestPhotoId);
- }
-
- if (bestPhotoFileId == 0) {
- mPhotoIdUpdate.bindNull(2);
- } else {
- mPhotoIdUpdate.bindLong(2, bestPhotoFileId);
- }
-
- mPhotoIdUpdate.bindLong(3, contactId);
- mPhotoIdUpdate.execute();
- }
-
- private interface PhotoFileQuery {
- final String[] COLUMNS = new String[] {
- PhotoFiles.HEIGHT,
- PhotoFiles.WIDTH,
- PhotoFiles.FILESIZE
- };
-
- int HEIGHT = 0;
- int WIDTH = 1;
- int FILESIZE = 2;
- }
-
- private class PhotoEntry implements Comparable<PhotoEntry> {
- // Pixel count (width * height) for the image.
- final int pixelCount;
-
- // File size (in bytes) of the image. Not populated if the image is a thumbnail.
- final int fileSize;
-
- private PhotoEntry(int pixelCount, int fileSize) {
- this.pixelCount = pixelCount;
- this.fileSize = fileSize;
- }
-
- @Override
- public int compareTo(PhotoEntry pe) {
- if (pe == null) {
- return -1;
- }
- if (pixelCount == pe.pixelCount) {
- return pe.fileSize - fileSize;
- } else {
- return pe.pixelCount - pixelCount;
- }
- }
- }
-
- private PhotoEntry getPhotoMetadata(SQLiteDatabase db, long photoFileId) {
- if (photoFileId == 0) {
- // Assume standard thumbnail size. Don't bother getting a file size for priority;
- // we should fall back to photo priority resolver if all we have are thumbnails.
- int thumbDim = mContactsProvider.getMaxThumbnailDim();
- return new PhotoEntry(thumbDim * thumbDim, 0);
- } else {
- Cursor c = db.query(Tables.PHOTO_FILES, PhotoFileQuery.COLUMNS, PhotoFiles._ID + "=?",
- new String[]{String.valueOf(photoFileId)}, null, null, null);
- try {
- if (c.getCount() == 1) {
- c.moveToFirst();
- int pixelCount =
- c.getInt(PhotoFileQuery.HEIGHT) * c.getInt(PhotoFileQuery.WIDTH);
- return new PhotoEntry(pixelCount, c.getInt(PhotoFileQuery.FILESIZE));
- }
- } finally {
- c.close();
- }
- }
- return new PhotoEntry(0, 0);
- }
-
- private interface DisplayNameQuery {
- String SQL_HAS_SUPER_PRIMARY_NAME =
- " EXISTS(SELECT 1 " +
- " FROM " + Tables.DATA + " d " +
- " WHERE d." + DataColumns.MIMETYPE_ID + "=? " +
- " AND d." + Data.RAW_CONTACT_ID + "=" + Views.RAW_CONTACTS
- + "." + RawContacts._ID +
- " AND d." + Data.IS_SUPER_PRIMARY + "=1)";
-
- String SQL =
- "SELECT "
- + RawContacts._ID + ","
- + RawContactsColumns.DISPLAY_NAME + ","
- + RawContactsColumns.DISPLAY_NAME_SOURCE + ","
- + SQL_HAS_SUPER_PRIMARY_NAME + ","
- + RawContacts.SOURCE_ID + ","
- + RawContacts.ACCOUNT_TYPE_AND_DATA_SET +
- " FROM " + Views.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=? ";
-
- int _ID = 0;
- int DISPLAY_NAME = 1;
- int DISPLAY_NAME_SOURCE = 2;
- int HAS_SUPER_PRIMARY_NAME = 3;
- int SOURCE_ID = 4;
- int ACCOUNT_TYPE_AND_DATA_SET = 5;
- }
-
- public void updateDisplayNameForRawContact(SQLiteDatabase db, long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- updateDisplayNameForContact(db, contactId);
- }
-
- public void updateDisplayNameForContact(SQLiteDatabase db, long contactId) {
- boolean lookupKeyUpdateNeeded = false;
-
- mDisplayNameCandidate.clear();
-
- mSelectionArgs2[0] = String.valueOf(mDbHelper.getMimeTypeIdForStructuredName());
- mSelectionArgs2[1] = String.valueOf(contactId);
- final Cursor c = db.rawQuery(DisplayNameQuery.SQL, mSelectionArgs2);
- try {
- while (c.moveToNext()) {
- long rawContactId = c.getLong(DisplayNameQuery._ID);
- String displayName = c.getString(DisplayNameQuery.DISPLAY_NAME);
- int displayNameSource = c.getInt(DisplayNameQuery.DISPLAY_NAME_SOURCE);
- int isNameSuperPrimary = c.getInt(DisplayNameQuery.HAS_SUPER_PRIMARY_NAME);
- String accountTypeAndDataSet = c.getString(
- DisplayNameQuery.ACCOUNT_TYPE_AND_DATA_SET);
- processDisplayNameCandidate(rawContactId, displayName, displayNameSource,
- mContactsProvider.isWritableAccountWithDataSet(accountTypeAndDataSet),
- isNameSuperPrimary != 0);
-
- // If the raw contact has no source id, the lookup key is based on the display
- // name, so the lookup key needs to be updated.
- lookupKeyUpdateNeeded |= c.isNull(DisplayNameQuery.SOURCE_ID);
- }
- } finally {
- c.close();
- }
-
- if (mDisplayNameCandidate.rawContactId != -1) {
- mDisplayNameUpdate.bindLong(1, mDisplayNameCandidate.rawContactId);
- mDisplayNameUpdate.bindLong(2, contactId);
- mDisplayNameUpdate.execute();
- }
-
- if (lookupKeyUpdateNeeded) {
- updateLookupKeyForContact(db, contactId);
- }
- }
-
-
- /**
- * Updates the {@link Contacts#HAS_PHONE_NUMBER} flag for the aggregate contact containing the
- * specified raw contact.
- */
- public void updateHasPhoneNumber(SQLiteDatabase db, long rawContactId) {
-
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- final SQLiteStatement hasPhoneNumberUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.HAS_PHONE_NUMBER + "="
- + "(SELECT (CASE WHEN COUNT(*)=0 THEN 0 ELSE 1 END)"
- + " FROM " + Tables.DATA_JOIN_RAW_CONTACTS
- + " WHERE " + DataColumns.MIMETYPE_ID + "=?"
- + " AND " + Phone.NUMBER + " NOT NULL"
- + " AND " + RawContacts.CONTACT_ID + "=?)" +
- " WHERE " + Contacts._ID + "=?");
- try {
- hasPhoneNumberUpdate.bindLong(1, mDbHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE));
- hasPhoneNumberUpdate.bindLong(2, contactId);
- hasPhoneNumberUpdate.bindLong(3, contactId);
- hasPhoneNumberUpdate.execute();
- } finally {
- hasPhoneNumberUpdate.close();
- }
- }
-
- private interface LookupKeyQuery {
- String TABLE = Views.RAW_CONTACTS;
- String[] COLUMNS = new String[] {
- RawContacts._ID,
- RawContactsColumns.DISPLAY_NAME,
- RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
- RawContacts.ACCOUNT_NAME,
- RawContacts.SOURCE_ID,
- };
-
- int ID = 0;
- int DISPLAY_NAME = 1;
- int ACCOUNT_TYPE_AND_DATA_SET = 2;
- int ACCOUNT_NAME = 3;
- int SOURCE_ID = 4;
- }
-
- public void updateLookupKeyForRawContact(SQLiteDatabase db, long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- updateLookupKeyForContact(db, contactId);
- }
-
- private void updateLookupKeyForContact(SQLiteDatabase db, long contactId) {
- String lookupKey = computeLookupKeyForContact(db, contactId);
-
- if (lookupKey == null) {
- mLookupKeyUpdate.bindNull(1);
- } else {
- mLookupKeyUpdate.bindString(1, Uri.encode(lookupKey));
- }
- mLookupKeyUpdate.bindLong(2, contactId);
-
- mLookupKeyUpdate.execute();
- }
-
- protected String computeLookupKeyForContact(SQLiteDatabase db, long contactId) {
- StringBuilder sb = new StringBuilder();
- mSelectionArgs1[0] = String.valueOf(contactId);
- final Cursor c = db.query(LookupKeyQuery.TABLE, LookupKeyQuery.COLUMNS,
- RawContacts.CONTACT_ID + "=?", mSelectionArgs1, null, null, RawContacts._ID);
- try {
- while (c.moveToNext()) {
- ContactLookupKey.appendToLookupKey(sb,
- c.getString(LookupKeyQuery.ACCOUNT_TYPE_AND_DATA_SET),
- c.getString(LookupKeyQuery.ACCOUNT_NAME),
- c.getLong(LookupKeyQuery.ID),
- c.getString(LookupKeyQuery.SOURCE_ID),
- c.getString(LookupKeyQuery.DISPLAY_NAME));
- }
- } finally {
- c.close();
- }
- return sb.length() == 0 ? null : sb.toString();
- }
-
- /**
- * Execute {@link SQLiteStatement} that will update the
- * {@link Contacts#STARRED} flag for the given {@link RawContacts#_ID}.
- */
- public void updateStarred(long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- mStarredUpdate.bindLong(1, contactId);
- mStarredUpdate.execute();
- }
-
- /**
- * Execute {@link SQLiteStatement} that will update the
- * {@link Contacts#PINNED} flag for the given {@link RawContacts#_ID}.
- */
- public void updatePinned(long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
- mPinnedUpdate.bindLong(1, contactId);
- mPinnedUpdate.execute();
- }
-
- /**
- * Finds matching contacts and returns a cursor on those.
- */
- public Cursor queryAggregationSuggestions(SQLiteQueryBuilder qb,
- String[] projection, long contactId, int maxSuggestions, String filter,
- ArrayList<AggregationSuggestionParameter> parameters) {
- final SQLiteDatabase db = mDbHelper.getReadableDatabase();
- db.beginTransaction();
- try {
- List<MatchScore> bestMatches = findMatchingContacts(db, contactId, parameters);
- return queryMatchingContacts(qb, db, projection, bestMatches, maxSuggestions, filter);
- } finally {
- db.endTransaction();
- }
- }
-
- private interface ContactIdQuery {
- String[] COLUMNS = new String[] {
- Contacts._ID
- };
-
- int _ID = 0;
- }
-
- /**
- * Loads contacts with specified IDs and returns them in the order of IDs in the
- * supplied list.
- */
- private Cursor queryMatchingContacts(SQLiteQueryBuilder qb, SQLiteDatabase db,
- String[] projection, List<MatchScore> bestMatches, int maxSuggestions, String filter) {
- StringBuilder sb = new StringBuilder();
- sb.append(Contacts._ID);
- sb.append(" IN (");
- for (int i = 0; i < bestMatches.size(); i++) {
- MatchScore matchScore = bestMatches.get(i);
- if (i != 0) {
- sb.append(",");
- }
- sb.append(matchScore.getContactId());
- }
- sb.append(")");
-
- if (!TextUtils.isEmpty(filter)) {
- sb.append(" AND " + Contacts._ID + " IN ");
- mContactsProvider.appendContactFilterAsNestedQuery(sb, filter);
- }
-
- // Run a query and find ids of best matching contacts satisfying the filter (if any)
- HashSet<Long> foundIds = new HashSet<Long>();
- Cursor cursor = db.query(qb.getTables(), ContactIdQuery.COLUMNS, sb.toString(),
- null, null, null, null);
- try {
- while(cursor.moveToNext()) {
- foundIds.add(cursor.getLong(ContactIdQuery._ID));
- }
- } finally {
- cursor.close();
- }
-
- // Exclude all contacts that did not match the filter
- Iterator<MatchScore> iter = bestMatches.iterator();
- while (iter.hasNext()) {
- long id = iter.next().getContactId();
- if (!foundIds.contains(id)) {
- iter.remove();
- }
- }
-
- // Limit the number of returned suggestions
- final List<MatchScore> limitedMatches;
- if (bestMatches.size() > maxSuggestions) {
- limitedMatches = bestMatches.subList(0, maxSuggestions);
- } else {
- limitedMatches = bestMatches;
- }
-
- // Build an in-clause with the remaining contact IDs
- sb.setLength(0);
- sb.append(Contacts._ID);
- sb.append(" IN (");
- for (int i = 0; i < limitedMatches.size(); i++) {
- MatchScore matchScore = limitedMatches.get(i);
- if (i != 0) {
- sb.append(",");
- }
- sb.append(matchScore.getContactId());
- }
- sb.append(")");
-
- // Run the final query with the required projection and contact IDs found by the first query
- cursor = qb.query(db, projection, sb.toString(), null, null, null, Contacts._ID);
-
- // Build a sorted list of discovered IDs
- ArrayList<Long> sortedContactIds = new ArrayList<Long>(limitedMatches.size());
- for (MatchScore matchScore : limitedMatches) {
- sortedContactIds.add(matchScore.getContactId());
- }
-
- Collections.sort(sortedContactIds);
-
- // Map cursor indexes according to the descending order of match scores
- int[] positionMap = new int[limitedMatches.size()];
- for (int i = 0; i < positionMap.length; i++) {
- long id = limitedMatches.get(i).getContactId();
- positionMap[i] = sortedContactIds.indexOf(id);
- }
-
- return new ReorderingCursorWrapper(cursor, positionMap);
- }
-
/**
* Finds contacts with data matches and returns a list of {@link MatchScore}'s in the
* descending order of match score.
* @param parameters
*/
- private List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId,
+ protected List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId,
ArrayList<AggregationSuggestionParameter> parameters) {
MatchCandidateList candidates = new MatchCandidateList();
diff --git a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
index 879dcbc..ce54bec 100644
--- a/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
+++ b/src/com/android/providers/contacts/aggregation/ContactAggregator2.java
@@ -16,273 +16,60 @@
package com.android.providers.contacts.aggregation;
-import static com.android.providers.contacts.aggregation.util.RawContactMatcher.SCORE_THRESHOLD_PRIMARY;
-import static com.android.providers.contacts.aggregation.util.RawContactMatcher.SCORE_THRESHOLD_SECONDARY;
-import static com.android.providers.contacts.aggregation.util.RawContactMatcher.SCORE_THRESHOLD_SUGGEST;
-
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.database.sqlite.SQLiteStatement;
-import android.net.Uri;
import android.provider.ContactsContract.AggregationExceptions;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Identity;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
-import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Contacts.AggregationSuggestions;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.DisplayNameSources;
import android.provider.ContactsContract.FullNameStyle;
import android.provider.ContactsContract.PhotoFiles;
-import android.provider.ContactsContract.PinnedPositions;
import android.provider.ContactsContract.RawContacts;
-import android.provider.ContactsContract.StatusUpdates;
import android.text.TextUtils;
-import android.util.EventLog;
import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.providers.contacts.ContactLookupKey;
import com.android.providers.contacts.ContactsDatabaseHelper;
-import com.android.providers.contacts.ContactsDatabaseHelper.AccountsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.AggregatedPresenceColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.ContactsColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
-import com.android.providers.contacts.ContactsDatabaseHelper.PhoneLookupColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.PresenceColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.RawContactsColumns;
-import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
-import com.android.providers.contacts.ContactsDatabaseHelper.Views;
import com.android.providers.contacts.ContactsProvider2;
-import com.android.providers.contacts.NameLookupBuilder;
-import com.android.providers.contacts.NameNormalizer;
import com.android.providers.contacts.NameSplitter;
import com.android.providers.contacts.PhotoPriorityResolver;
-import com.android.providers.contacts.ReorderingCursorWrapper;
import com.android.providers.contacts.TransactionContext;
import com.android.providers.contacts.aggregation.util.CommonNicknameCache;
import com.android.providers.contacts.aggregation.util.ContactAggregatorHelper;
+import com.android.providers.contacts.aggregation.util.MatchScore;
import com.android.providers.contacts.aggregation.util.RawContactMatcher;
-import com.android.providers.contacts.aggregation.util.RawContactMatcher.MatchScore;
import com.android.providers.contacts.aggregation.util.RawContactMatchingCandidates;
import com.android.providers.contacts.database.ContactsTableUtil;
-import com.android.providers.contacts.util.Clock;
-
-import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.HashMultimap;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import static com.android.providers.contacts.aggregation.util.RawContactMatcher
+ .SCORE_THRESHOLD_PRIMARY;
+import static com.android.providers.contacts.aggregation.util.RawContactMatcher
+ .SCORE_THRESHOLD_SECONDARY;
+import static com.android.providers.contacts.aggregation.util.RawContactMatcher
+ .SCORE_THRESHOLD_SUGGEST;
+
/**
* ContactAggregator2 deals with aggregating contact information with sufficient matching data
* points. E.g., two John Doe contacts with same phone numbers are presumed to be the same
* person unless the user declares otherwise.
*/
-public class ContactAggregator2 {
-
- private static final String TAG = "ContactAggregator2";
-
- private static final boolean DEBUG_LOGGING = Log.isLoggable(TAG, Log.DEBUG);
- private static final boolean VERBOSE_LOGGING = Log.isLoggable(TAG, Log.VERBOSE);
-
- private static final String STRUCTURED_NAME_BASED_LOOKUP_SQL =
- NameLookupColumns.NAME_TYPE + " IN ("
- + NameLookupType.NAME_EXACT + ","
- + NameLookupType.NAME_VARIANT + ","
- + NameLookupType.NAME_COLLATION_KEY + ")";
-
-
- /**
- * SQL statement that sets the {@link ContactsColumns#LAST_STATUS_UPDATE_ID} column
- * on the contact to point to the latest social status update.
- */
- private static final String UPDATE_LAST_STATUS_UPDATE_ID_SQL =
- "UPDATE " + Tables.CONTACTS +
- " SET " + ContactsColumns.LAST_STATUS_UPDATE_ID + "=" +
- "(SELECT " + DataColumns.CONCRETE_ID +
- " FROM " + Tables.STATUS_UPDATES +
- " JOIN " + Tables.DATA +
- " ON (" + StatusUpdatesColumns.DATA_ID + "="
- + DataColumns.CONCRETE_ID + ")" +
- " JOIN " + Tables.RAW_CONTACTS +
- " ON (" + DataColumns.CONCRETE_RAW_CONTACT_ID + "="
- + RawContactsColumns.CONCRETE_ID + ")" +
- " WHERE " + RawContacts.CONTACT_ID + "=?" +
- " ORDER BY " + StatusUpdates.STATUS_TIMESTAMP + " DESC,"
- + StatusUpdates.STATUS +
- " LIMIT 1)" +
- " WHERE " + ContactsColumns.CONCRETE_ID + "=?";
-
- // From system/core/logcat/event-log-tags
- // aggregator [time, count] will be logged for each aggregator cycle.
- // For the query (as opposed to the merge), count will be negative
- public static final int LOG_SYNC_CONTACTS_AGGREGATION = 2747;
-
- // If we encounter more than this many contacts with matching names, aggregate only this many
- private static final int PRIMARY_HIT_LIMIT = 15;
- private static final String PRIMARY_HIT_LIMIT_STRING = String.valueOf(PRIMARY_HIT_LIMIT);
-
- // If we encounter more than this many contacts with matching phone number or email,
- // don't attempt to aggregate - this is likely an error or a shared corporate data element.
- private static final int SECONDARY_HIT_LIMIT = 20;
- private static final String SECONDARY_HIT_LIMIT_STRING = String.valueOf(SECONDARY_HIT_LIMIT);
-
- // If we encounter no less than this many raw contacts in the best matching contact during
- // aggregation, don't attempt to aggregate - this is likely an error or a shared corporate
- // data element.
- @VisibleForTesting
- static final int AGGREGATION_CONTACT_SIZE_LIMIT = 50;
-
- // If we encounter more than this many contacts with matching name during aggregation
- // suggestion lookup, ignore the remaining results.
- private static final int FIRST_LETTER_SUGGESTION_HIT_LIMIT = 100;
+public class ContactAggregator2 extends AbstractContactAggregator {
// Possible operation types for contacts aggregation.
private static final int CREATE_NEW_CONTACT = 1;
private static final int KEEP_INTACT = 0;
private static final int RE_AGGREGATE = -1;
- private final ContactsProvider2 mContactsProvider;
- private final ContactsDatabaseHelper mDbHelper;
- private final PhotoPriorityResolver mPhotoPriorityResolver;
- private final NameSplitter mNameSplitter;
- private final CommonNicknameCache mCommonNicknameCache;
- private final MatchCandidateList mCandidates = new MatchCandidateList();
private final RawContactMatcher mMatcher = new RawContactMatcher();
- private final DisplayNameCandidate mDisplayNameCandidate = new DisplayNameCandidate();
-
- private boolean mEnabled = true;
-
- /** Precompiled sql statement for setting an aggregated presence */
- private SQLiteStatement mAggregatedPresenceReplace;
- private SQLiteStatement mPresenceContactIdUpdate;
- private SQLiteStatement mRawContactCountQuery;
- private SQLiteStatement mAggregatedPresenceDelete;
- private SQLiteStatement mMarkForAggregation;
- private SQLiteStatement mPhotoIdUpdate;
- private SQLiteStatement mDisplayNameUpdate;
- private SQLiteStatement mLookupKeyUpdate;
- private SQLiteStatement mStarredUpdate;
- private SQLiteStatement mPinnedUpdate;
- private SQLiteStatement mContactIdAndMarkAggregatedUpdate;
- private SQLiteStatement mContactIdUpdate;
- private SQLiteStatement mMarkAggregatedUpdate;
- private SQLiteStatement mContactUpdate;
- private SQLiteStatement mContactInsert;
- private SQLiteStatement mResetPinnedForRawContact;
-
- private HashMap<Long, Integer> mRawContactsMarkedForAggregation = Maps.newHashMap();
-
- private String[] mSelectionArgs1 = new String[1];
- private String[] mSelectionArgs2 = new String[2];
-
- private long mMimeTypeIdIdentity;
- private long mMimeTypeIdEmail;
- private long mMimeTypeIdPhoto;
- private long mMimeTypeIdPhone;
- private String mRawContactsQueryByRawContactId;
- private String mRawContactsQueryByContactId;
- private StringBuilder mSb = new StringBuilder();
-
-
- /**
- * Parameter for the suggestion lookup query.
- */
- public static final class AggregationSuggestionParameter {
- public final String kind;
- public final String value;
-
- public AggregationSuggestionParameter(String kind, String value) {
- this.kind = kind;
- this.value = value;
- }
- }
-
- /**
- * Captures a potential match for a given name. The matching algorithm
- * constructs a bunch of NameMatchCandidate objects for various potential matches
- * and then executes the search in bulk.
- */
- private static class NameMatchCandidate {
- String mName;
- int mLookupType;
-
- public NameMatchCandidate(String name, int nameLookupType) {
- mName = name;
- mLookupType = nameLookupType;
- }
- }
-
- /**
- * A list of {@link NameMatchCandidate} that keeps its elements even when the list is
- * truncated. This is done for optimization purposes to avoid excessive object allocation.
- */
- private static class MatchCandidateList {
- private final ArrayList<NameMatchCandidate> mList = new ArrayList<NameMatchCandidate>();
- private int mCount;
-
- /**
- * Adds a {@link NameMatchCandidate} element or updates the next one if it already exists.
- */
- public void add(String name, int nameLookupType) {
- if (mCount >= mList.size()) {
- mList.add(new NameMatchCandidate(name, nameLookupType));
- } else {
- NameMatchCandidate candidate = mList.get(mCount);
- candidate.mName = name;
- candidate.mLookupType = nameLookupType;
- }
- mCount++;
- }
-
- public void clear() {
- mCount = 0;
- }
-
- public boolean isEmpty() {
- return mCount == 0;
- }
- }
-
- /**
- * A convenience class used in the algorithm that figures out which of available
- * display names to use for an aggregate contact.
- */
- private static class DisplayNameCandidate {
- long rawContactId;
- String displayName;
- int displayNameSource;
- boolean isNameSuperPrimary;
- boolean writableAccount;
-
- public DisplayNameCandidate() {
- clear();
- }
-
- public void clear() {
- rawContactId = -1;
- displayName = null;
- displayNameSource = DisplayNameSources.UNDEFINED;
- isNameSuperPrimary = false;
- writableAccount = false;
- }
- }
/**
* Constructor.
@@ -291,221 +78,8 @@ public class ContactAggregator2 {
ContactsDatabaseHelper contactsDatabaseHelper,
PhotoPriorityResolver photoPriorityResolver, NameSplitter nameSplitter,
CommonNicknameCache commonNicknameCache) {
- mContactsProvider = contactsProvider;
- mDbHelper = contactsDatabaseHelper;
- mPhotoPriorityResolver = photoPriorityResolver;
- mNameSplitter = nameSplitter;
- mCommonNicknameCache = commonNicknameCache;
-
- SQLiteDatabase db = mDbHelper.getReadableDatabase();
-
- // Since we have no way of determining which custom status was set last,
- // we'll just pick one randomly. We are using MAX as an approximation of randomness
- final String replaceAggregatePresenceSql =
- "INSERT OR REPLACE INTO " + Tables.AGGREGATED_PRESENCE + "("
- + AggregatedPresenceColumns.CONTACT_ID + ", "
- + StatusUpdates.PRESENCE + ", "
- + StatusUpdates.CHAT_CAPABILITY + ")"
- + " SELECT " + PresenceColumns.CONTACT_ID + ","
- + StatusUpdates.PRESENCE + ","
- + StatusUpdates.CHAT_CAPABILITY
- + " FROM " + Tables.PRESENCE
- + " WHERE "
- + " (" + StatusUpdates.PRESENCE
- + " * 10 + " + StatusUpdates.CHAT_CAPABILITY + ")"
- + " = (SELECT "
- + "MAX (" + StatusUpdates.PRESENCE
- + " * 10 + " + StatusUpdates.CHAT_CAPABILITY + ")"
- + " FROM " + Tables.PRESENCE
- + " WHERE " + PresenceColumns.CONTACT_ID
- + "=?)"
- + " AND " + PresenceColumns.CONTACT_ID
- + "=?;";
- mAggregatedPresenceReplace = db.compileStatement(replaceAggregatePresenceSql);
-
- mRawContactCountQuery = db.compileStatement(
- "SELECT COUNT(" + RawContacts._ID + ")" +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=?"
- + " AND " + RawContacts._ID + "<>?");
-
- mAggregatedPresenceDelete = db.compileStatement(
- "DELETE FROM " + Tables.AGGREGATED_PRESENCE +
- " WHERE " + AggregatedPresenceColumns.CONTACT_ID + "=?");
-
- mMarkForAggregation = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.AGGREGATION_NEEDED + "=1" +
- " WHERE " + RawContacts._ID + "=?"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0");
-
- mPhotoIdUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.PHOTO_ID + "=?," + Contacts.PHOTO_FILE_ID + "=? " +
- " WHERE " + Contacts._ID + "=?");
-
- mDisplayNameUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.NAME_RAW_CONTACT_ID + "=? " +
- " WHERE " + Contacts._ID + "=?");
-
- mLookupKeyUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.LOOKUP_KEY + "=? " +
- " WHERE " + Contacts._ID + "=?");
-
- mStarredUpdate = db.compileStatement("UPDATE " + Tables.CONTACTS + " SET "
- + Contacts.STARRED + "=(SELECT (CASE WHEN COUNT(" + RawContacts.STARRED
- + ")=0 THEN 0 ELSE 1 END) FROM " + Tables.RAW_CONTACTS + " WHERE "
- + RawContacts.CONTACT_ID + "=" + ContactsColumns.CONCRETE_ID + " AND "
- + RawContacts.STARRED + "=1)" + " WHERE " + Contacts._ID + "=?");
-
- mPinnedUpdate = db.compileStatement("UPDATE " + Tables.CONTACTS + " SET "
- + Contacts.PINNED + " = IFNULL((SELECT MIN(" + RawContacts.PINNED + ") FROM "
- + Tables.RAW_CONTACTS + " WHERE " + RawContacts.CONTACT_ID + "="
- + ContactsColumns.CONCRETE_ID + " AND " + RawContacts.PINNED + ">"
- + PinnedPositions.UNPINNED + ")," + PinnedPositions.UNPINNED + ") "
- + "WHERE " + Contacts._ID + "=?");
-
- mContactIdAndMarkAggregatedUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.CONTACT_ID + "=?, "
- + RawContactsColumns.AGGREGATION_NEEDED + "=0" +
- " WHERE " + RawContacts._ID + "=?");
-
- mContactIdUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.CONTACT_ID + "=?" +
- " WHERE " + RawContacts._ID + "=?");
-
- mMarkAggregatedUpdate = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContactsColumns.AGGREGATION_NEEDED + "=0" +
- " WHERE " + RawContacts._ID + "=?");
-
- mPresenceContactIdUpdate = db.compileStatement(
- "UPDATE " + Tables.PRESENCE +
- " SET " + PresenceColumns.CONTACT_ID + "=?" +
- " WHERE " + PresenceColumns.RAW_CONTACT_ID + "=?");
-
- mContactUpdate = db.compileStatement(ContactReplaceSqlStatement.UPDATE_SQL);
- mContactInsert = db.compileStatement(ContactReplaceSqlStatement.INSERT_SQL);
-
- mResetPinnedForRawContact = db.compileStatement(
- "UPDATE " + Tables.RAW_CONTACTS +
- " SET " + RawContacts.PINNED + "=" + PinnedPositions.UNPINNED +
- " WHERE " + RawContacts._ID + "=?");
-
- mMimeTypeIdEmail = mDbHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
- mMimeTypeIdIdentity = mDbHelper.getMimeTypeId(Identity.CONTENT_ITEM_TYPE);
- mMimeTypeIdPhoto = mDbHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
- mMimeTypeIdPhone = mDbHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE);
-
- // Query used to retrieve data from raw contacts to populate the corresponding aggregate
- mRawContactsQueryByRawContactId = String.format(Locale.US,
- RawContactsQuery.SQL_FORMAT_BY_RAW_CONTACT_ID,
- mDbHelper.getMimeTypeIdForStructuredName(), mMimeTypeIdPhoto, mMimeTypeIdPhone);
-
- mRawContactsQueryByContactId = String.format(Locale.US,
- RawContactsQuery.SQL_FORMAT_BY_CONTACT_ID,
- mDbHelper.getMimeTypeIdForStructuredName(), mMimeTypeIdPhoto, mMimeTypeIdPhone);
- }
-
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- }
-
- public boolean isEnabled() {
- return mEnabled;
- }
-
- private interface AggregationQuery {
- String SQL =
- "SELECT " + RawContacts._ID + "," + RawContacts.CONTACT_ID +
- ", " + RawContactsColumns.ACCOUNT_ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContacts._ID + " IN(";
-
- int _ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
- }
-
- /**
- * Aggregate all raw contacts that were marked for aggregation in the current transaction.
- * Call just before committing the transaction.
- */
- public void aggregateInTransaction(TransactionContext txContext, SQLiteDatabase db) {
- final int markedCount = mRawContactsMarkedForAggregation.size();
- if (markedCount == 0) {
- return;
- }
-
- final long start = System.currentTimeMillis();
- if (DEBUG_LOGGING) {
- Log.d(TAG, "aggregateInTransaction for " + markedCount + " contacts");
- }
-
- EventLog.writeEvent(LOG_SYNC_CONTACTS_AGGREGATION, start, -markedCount);
-
- int index = 0;
-
- // We don't use the cached string builder (namely mSb) here, as this string can be very
- // long when upgrading (where we re-aggregate all visible contacts) and StringBuilder won't
- // shrink the internal storage.
- // Note: don't use selection args here. We just include all IDs directly in the selection,
- // because there's a limit for the number of parameters in a query.
- final StringBuilder sbQuery = new StringBuilder();
- sbQuery.append(AggregationQuery.SQL);
- for (long rawContactId : mRawContactsMarkedForAggregation.keySet()) {
- if (index > 0) {
- sbQuery.append(',');
- }
- sbQuery.append(rawContactId);
- index++;
- }
-
- sbQuery.append(')');
-
- final long[] rawContactIds;
- final long[] contactIds;
- final long[] accountIds;
- final int actualCount;
- final Cursor c = db.rawQuery(sbQuery.toString(), null);
- try {
- actualCount = c.getCount();
- rawContactIds = new long[actualCount];
- contactIds = new long[actualCount];
- accountIds = new long[actualCount];
-
- index = 0;
- while (c.moveToNext()) {
- rawContactIds[index] = c.getLong(AggregationQuery._ID);
- contactIds[index] = c.getLong(AggregationQuery.CONTACT_ID);
- accountIds[index] = c.getLong(AggregationQuery.ACCOUNT_ID);
- index++;
- }
- } finally {
- c.close();
- }
-
- if (DEBUG_LOGGING) {
- Log.d(TAG, "aggregateInTransaction: initial query done.");
- }
-
- for (int i = 0; i < actualCount; i++) {
- aggregateContact(txContext, db, rawContactIds[i], accountIds[i], contactIds[i],
- mCandidates, mMatcher);
- }
-
- long elapsedTime = System.currentTimeMillis() - start;
- EventLog.writeEvent(LOG_SYNC_CONTACTS_AGGREGATION, elapsedTime, actualCount);
-
- if (DEBUG_LOGGING) {
- Log.d(TAG, "Contact aggregation complete: " + actualCount +
- (actualCount == 0 ? "" : ", " + (elapsedTime / actualCount)
- + " ms per raw contact"));
- }
+ super(contactsProvider, contactsDatabaseHelper, photoPriorityResolver, nameSplitter,
+ commonNicknameCache);
}
@SuppressWarnings("deprecation")
@@ -540,35 +114,6 @@ public class ContactAggregator2 {
}
}
- public void clearPendingAggregations() {
- // HashMap woulnd't shrink the internal table once expands it, so let's just re-create
- // a new one instead of clear()ing it.
- mRawContactsMarkedForAggregation = Maps.newHashMap();
- }
-
- public void markNewForAggregation(long rawContactId, int aggregationMode) {
- mRawContactsMarkedForAggregation.put(rawContactId, aggregationMode);
- }
-
- public void markForAggregation(long rawContactId, int aggregationMode, boolean force) {
- final int effectiveAggregationMode;
- if (!force && mRawContactsMarkedForAggregation.containsKey(rawContactId)) {
- // As per ContactsContract documentation, default aggregation mode
- // does not override a previously set mode
- if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
- effectiveAggregationMode = mRawContactsMarkedForAggregation.get(rawContactId);
- } else {
- effectiveAggregationMode = aggregationMode;
- }
- } else {
- mMarkForAggregation.bindLong(1, rawContactId);
- mMarkForAggregation.execute();
- effectiveAggregationMode = aggregationMode;
- }
-
- mRawContactsMarkedForAggregation.put(rawContactId, effectiveAggregationMode);
- }
-
private static class RawContactIdAndAggregationModeQuery {
public static final String TABLE = Tables.RAW_CONTACTS;
@@ -581,168 +126,14 @@ public class ContactAggregator2 {
}
/**
- * Marks all constituent raw contacts of an aggregated contact for re-aggregation.
- */
- private void markContactForAggregation(SQLiteDatabase db, long contactId) {
- mSelectionArgs1[0] = String.valueOf(contactId);
- Cursor cursor = db.query(RawContactIdAndAggregationModeQuery.TABLE,
- RawContactIdAndAggregationModeQuery.COLUMNS,
- RawContactIdAndAggregationModeQuery.SELECTION, mSelectionArgs1, null, null, null);
- try {
- if (cursor.moveToFirst()) {
- long rawContactId = cursor.getLong(RawContactIdAndAggregationModeQuery._ID);
- int aggregationMode = cursor.getInt(
- RawContactIdAndAggregationModeQuery.AGGREGATION_MODE);
- // Don't re-aggregate AGGREGATION_MODE_SUSPENDED / AGGREGATION_MODE_DISABLED.
- // (Also just ignore deprecated AGGREGATION_MODE_IMMEDIATE)
- if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
- markForAggregation(rawContactId, aggregationMode, true);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Mark all visible contacts for re-aggregation.
- *
- * - Set {@link RawContactsColumns#AGGREGATION_NEEDED} For all visible raw_contacts with
- * {@link RawContacts#AGGREGATION_MODE_DEFAULT}.
- * - Also put them into {@link #mRawContactsMarkedForAggregation}.
- */
- public int markAllVisibleForAggregation(SQLiteDatabase db) {
- final long start = System.currentTimeMillis();
-
- // Set AGGREGATION_NEEDED for all visible raw_cotnacts with AGGREGATION_MODE_DEFAULT.
- // (Don't re-aggregate AGGREGATION_MODE_SUSPENDED / AGGREGATION_MODE_DISABLED)
- db.execSQL("UPDATE " + Tables.RAW_CONTACTS + " SET " +
- RawContactsColumns.AGGREGATION_NEEDED + "=1" +
- " WHERE " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY +
- " AND " + RawContacts.AGGREGATION_MODE + "=" + RawContacts.AGGREGATION_MODE_DEFAULT
- );
-
- final int count;
- final Cursor cursor = db.rawQuery("SELECT " + RawContacts._ID +
- " FROM " + Tables.RAW_CONTACTS +
- " WHERE " + RawContactsColumns.AGGREGATION_NEEDED + "=1", null);
- try {
- count = cursor.getCount();
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- final long rawContactId = cursor.getLong(0);
- mRawContactsMarkedForAggregation.put(rawContactId,
- RawContacts.AGGREGATION_MODE_DEFAULT);
- }
- } finally {
- cursor.close();
- }
-
- final long end = System.currentTimeMillis();
- Log.i(TAG, "Marked all visible contacts for aggregation: " + count + " raw contacts, " +
- (end - start) + " ms");
- return count;
- }
-
- /**
- * Creates a new contact based on the given raw contact. Does not perform aggregation. Returns
- * the ID of the contact that was created.
- */
- public long onRawContactInsert(
- TransactionContext txContext, SQLiteDatabase db, long rawContactId) {
- long contactId = insertContact(db, rawContactId);
- setContactId(rawContactId, contactId);
- mDbHelper.updateContactVisible(txContext, contactId);
- return contactId;
- }
-
- protected long insertContact(SQLiteDatabase db, long rawContactId) {
- mSelectionArgs1[0] = String.valueOf(rawContactId);
- computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1, mContactInsert);
- return mContactInsert.executeInsert();
- }
-
- private static final class RawContactIdAndAccountQuery {
- public static final String TABLE = Tables.RAW_CONTACTS;
-
- public static final String[] COLUMNS = {
- RawContacts.CONTACT_ID,
- RawContactsColumns.ACCOUNT_ID
- };
-
- public static final String SELECTION = RawContacts._ID + "=?";
-
- public static final int CONTACT_ID = 0;
- public static final int ACCOUNT_ID = 1;
- }
-
- public void aggregateContact(
- TransactionContext txContext, SQLiteDatabase db, long rawContactId) {
- if (!mEnabled) {
- return;
- }
-
- MatchCandidateList candidates = new MatchCandidateList();
- RawContactMatcher matcher = new RawContactMatcher();
-
- long contactId = 0;
- long accountId = 0;
- 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);
- accountId = cursor.getLong(RawContactIdAndAccountQuery.ACCOUNT_ID);
- }
- } finally {
- cursor.close();
- }
-
- aggregateContact(txContext, db, rawContactId, accountId, contactId,
- candidates, matcher);
- }
-
- public void updateAggregateData(TransactionContext txContext, long contactId) {
- if (!mEnabled) {
- return;
- }
-
- final SQLiteDatabase db = mDbHelper.getWritableDatabase();
- computeAggregateData(db, contactId, mContactUpdate);
- mContactUpdate.bindLong(ContactReplaceSqlStatement.CONTACT_ID, contactId);
- mContactUpdate.execute();
-
- mDbHelper.updateContactVisible(txContext, contactId);
- updateAggregatedStatusUpdate(contactId);
- }
-
- private void updateAggregatedStatusUpdate(long contactId) {
- mAggregatedPresenceReplace.bindLong(1, contactId);
- mAggregatedPresenceReplace.bindLong(2, contactId);
- mAggregatedPresenceReplace.execute();
- updateLastStatusUpdateId(contactId);
- }
-
- /**
- * Adjusts the reference to the latest status update for the specified contact.
- */
- public void updateLastStatusUpdateId(long contactId) {
- String contactIdString = String.valueOf(contactId);
- mDbHelper.getWritableDatabase().execSQL(UPDATE_LAST_STATUS_UPDATE_ID_SQL,
- new String[]{contactIdString, contactIdString});
- }
-
- /**
* Given a specific raw contact, finds all matching raw contacts and re-aggregate them
* based on the matching connectivity.
*/
- private synchronized void aggregateContact(TransactionContext txContext, SQLiteDatabase db,
- long rawContactId, long accountId, long currentContactId, MatchCandidateList candidates,
- RawContactMatcher matcher) {
+ synchronized void aggregateContact(TransactionContext txContext, SQLiteDatabase db,
+ long rawContactId, long accountId, long currentContactId,
+ MatchCandidateList candidates) {
- if (VERBOSE_LOGGING) {
+ if (VERBOSE_LOGGING) {
Log.v(TAG, "aggregateContact: rid=" + rawContactId + " cid=" + currentContactId);
}
@@ -753,6 +144,7 @@ public class ContactAggregator2 {
aggregationMode = aggModeObject;
}
+ RawContactMatcher matcher = new RawContactMatcher();
RawContactMatchingCandidates matchingCandidates = new RawContactMatchingCandidates();
if (aggregationMode == RawContacts.AGGREGATION_MODE_DEFAULT) {
// Find the set of matching candidates
@@ -864,7 +256,7 @@ public class ContactAggregator2 {
* Clear the is_super_primary settings for these mime-types.
* {@code rawContactIds} should be a comma separated ID list.
*/
- private void clearSuperPrimarySetting(SQLiteDatabase db, String rawContactIds) {
+ 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 " +
@@ -909,72 +301,6 @@ public class ContactAggregator2 {
db.execSQL(superPrimaryUpdateSql, null);
}
- private interface RawContactMatchingSelectionStatement {
- String SELECT_COUNT = "SELECT count(*) " ;
- String SELECT_ID = "SELECT d1." + Data.RAW_CONTACT_ID + ",d2." + Data.RAW_CONTACT_ID ;
- }
-
- /**
- * Build sql to check if there is any identity match/mis-match between two sets of raw contact
- * ids on the same namespace.
- */
- private String buildIdentityMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
- boolean isIdentityMatching, boolean countOnly) {
- final String identityType = String.valueOf(mMimeTypeIdIdentity);
- final String matchingOperator = (isIdentityMatching) ? "=" : "!=";
- final String sql =
- " FROM " + Tables.DATA + " AS d1" +
- " JOIN " + Tables.DATA + " AS d2" +
- " ON (d1." + Identity.IDENTITY + matchingOperator +
- " d2." + Identity.IDENTITY + " AND" +
- " d1." + Identity.NAMESPACE + " = d2." + Identity.NAMESPACE + " )" +
- " WHERE d1." + DataColumns.MIMETYPE_ID + " = " + identityType +
- " AND d2." + DataColumns.MIMETYPE_ID + " = " + identityType +
- " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
- " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")";
- return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
- RawContactMatchingSelectionStatement.SELECT_ID + sql;
- }
-
- private String buildEmailMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
- boolean countOnly) {
- final String emailType = String.valueOf(mMimeTypeIdEmail);
- final String sql =
- " FROM " + Tables.DATA + " AS d1" +
- " JOIN " + Tables.DATA + " AS d2" +
- " ON lower(d1." + Email.ADDRESS + ")= lower(d2." + Email.ADDRESS + ")" +
- " WHERE d1." + DataColumns.MIMETYPE_ID + " = " + emailType +
- " AND d2." + DataColumns.MIMETYPE_ID + " = " + emailType +
- " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
- " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")";
- return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
- RawContactMatchingSelectionStatement.SELECT_ID + sql;
- }
-
- private String buildPhoneMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
- boolean countOnly) {
- // It's a bit tricker because it has to be consistent with
- // updateMatchScoresBasedOnPhoneMatches().
- final String phoneType = String.valueOf(mMimeTypeIdPhone);
- final String sql =
- " FROM " + Tables.PHONE_LOOKUP + " AS p1" +
- " JOIN " + Tables.DATA + " AS d1 ON " +
- "(d1." + Data._ID + "=p1." + PhoneLookupColumns.DATA_ID + ")" +
- " JOIN " + Tables.PHONE_LOOKUP + " AS p2 ON (p1." + PhoneLookupColumns.MIN_MATCH +
- "=p2." + PhoneLookupColumns.MIN_MATCH + ")" +
- " JOIN " + Tables.DATA + " AS d2 ON " +
- "(d2." + Data._ID + "=p2." + PhoneLookupColumns.DATA_ID + ")" +
- " WHERE d1." + DataColumns.MIMETYPE_ID + " = " + phoneType +
- " AND d2." + DataColumns.MIMETYPE_ID + " = " + phoneType +
- " AND d1." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet1 + ")" +
- " AND d2." + Data.RAW_CONTACT_ID + " IN (" + rawContactIdSet2 + ")" +
- " AND PHONE_NUMBERS_EQUAL(d1." + Phone.NUMBER + ",d2." + Phone.NUMBER + "," +
- String.valueOf(mDbHelper.getUseStrictPhoneNumberComparisonParameter()) +
- ")";
- return (countOnly) ? RawContactMatchingSelectionStatement.SELECT_COUNT + sql :
- RawContactMatchingSelectionStatement.SELECT_ID + sql;
- }
-
private String buildExceptionMatchingSql(String rawContactIdSet1, String rawContactIdSet2,
int aggregationType, boolean countOnly) {
final String idPairSelection = "SELECT " + AggregationExceptions.RAW_CONTACT_ID1 + ", " +
@@ -989,10 +315,6 @@ public class ContactAggregator2 {
idPairSelection + sql;
}
- private boolean isFirstColumnGreaterThanZero(SQLiteDatabase db, String query) {
- return DatabaseUtils.longForQuery(db, query, null) > 0;
- }
-
/**
* Re-aggregate rawContact of {@code rawContactId} and all the raw contacts of
* {@code matchingCandidates} into connected components. This only happens when a given
@@ -1121,84 +443,6 @@ public class ContactAggregator2 {
}
/**
- * Partition the given raw contact Ids to connected component based on aggregation exception,
- * identity matching, email matching or phone matching.
- */
- private Set<Set<Long>> findConnectedRawContacts(SQLiteDatabase db, Set<Long> rawContactIdSet) {
- // Connections between two raw contacts
- final Multimap<Long, Long> matchingRawIdPairs = HashMultimap.create();
- String rawContactIds = TextUtils.join(",", rawContactIdSet);
- findIdPairs(db, buildExceptionMatchingSql(rawContactIds, rawContactIds,
- AggregationExceptions.TYPE_KEEP_TOGETHER, /* countOnly =*/false),
- matchingRawIdPairs);
- findIdPairs(db, buildIdentityMatchingSql(rawContactIds, rawContactIds,
- /* isIdentityMatching =*/ true, /* countOnly =*/false), matchingRawIdPairs);
- findIdPairs(db, buildEmailMatchingSql(rawContactIds, rawContactIds, /* countOnly =*/false),
- matchingRawIdPairs);
- findIdPairs(db, buildPhoneMatchingSql(rawContactIds, rawContactIds, /* countOnly =*/false),
- matchingRawIdPairs);
-
- return ContactAggregatorHelper.findConnectedComponents(rawContactIdSet, matchingRawIdPairs);
- }
-
- /**
- * Given a query which will return two non-null IDs in the first two columns as results, this
- * method will put two entries into the given result map for each pair of different IDs, one
- * keyed by each ID.
- */
- private void findIdPairs(SQLiteDatabase db, String query, Multimap<Long, Long> results) {
- Cursor cursor = db.rawQuery(query, null);
- try {
- cursor.moveToPosition(-1);
- while (cursor.moveToNext()) {
- long idA = cursor.getLong(0);
- long idB = cursor.getLong(1);
- if (idA != idB) {
- results.put(idA, idB);
- results.put(idB, idA);
- }
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
- * Creates a new Contact for a given set of the raw contacts of {@code rawContactIds} if the
- * given contactId is null. Otherwise, regroup them into contact with {@code contactId}.
- */
- private void createContactForRawContacts(SQLiteDatabase db, TransactionContext txContext,
- Set<Long> rawContactIds, Long contactId) {
- if (rawContactIds.isEmpty()) {
- // No raw contact id is provided.
- return;
- }
-
- // If contactId is not provided, generates a new one.
- if (contactId == null) {
- mSelectionArgs1[0]= String.valueOf(rawContactIds.iterator().next());
- computeAggregateData(db, mRawContactsQueryByRawContactId, mSelectionArgs1,
- mContactInsert);
- contactId = mContactInsert.executeInsert();
- }
- for (Long rawContactId : rawContactIds) {
- // Regrouped contacts should automatically be unpinned.
- unpinRawContact(rawContactId);
- setContactIdAndMarkAggregated(rawContactId, contactId);
- setPresenceContactId(rawContactId, contactId);
- }
- updateAggregateData(txContext, contactId);
- }
-
- private static class RawContactIdQuery {
- public static final String TABLE = Tables.RAW_CONTACTS;
- public static final String[] COLUMNS = {RawContacts._ID, RawContactsColumns.ACCOUNT_ID };
- public static final String SELECTION = RawContacts.CONTACT_ID + "=?";
- public static final int RAW_CONTACT_ID = 0;
- public static final int ACCOUNT_ID = 1;
- }
-
- /**
* Ensures that automatic aggregation rules are followed after a contact
* becomes visible or invisible. Specifically, consider this case: there are
* three contacts named Foo. Two of them come from account A1 and one comes
@@ -1254,119 +498,6 @@ public class ContactAggregator2 {
}
/**
- * Updates the contact ID for the specified contact.
- */
- protected void setContactId(long rawContactId, long contactId) {
- mContactIdUpdate.bindLong(1, contactId);
- mContactIdUpdate.bindLong(2, rawContactId);
- mContactIdUpdate.execute();
- }
-
- /**
- * Marks the specified raw contact ID as aggregated
- */
- private void markAggregated(long rawContactId) {
- mMarkAggregatedUpdate.bindLong(1, rawContactId);
- mMarkAggregatedUpdate.execute();
- }
-
- /**
- * Updates the contact ID for the specified contact and marks the raw contact as aggregated.
- */
- private void setContactIdAndMarkAggregated(long rawContactId, long contactId) {
- mContactIdAndMarkAggregatedUpdate.bindLong(1, contactId);
- mContactIdAndMarkAggregatedUpdate.bindLong(2, rawContactId);
- mContactIdAndMarkAggregatedUpdate.execute();
- }
-
- private void setPresenceContactId(long rawContactId, long contactId) {
- mPresenceContactIdUpdate.bindLong(1, contactId);
- mPresenceContactIdUpdate.bindLong(2, rawContactId);
- mPresenceContactIdUpdate.execute();
- }
-
- private void unpinRawContact(long rawContactId) {
- mResetPinnedForRawContact.bindLong(1, rawContactId);
- mResetPinnedForRawContact.execute();
- }
-
- interface AggregateExceptionPrefetchQuery {
- String TABLE = Tables.AGGREGATION_EXCEPTIONS;
-
- String[] COLUMNS = {
- AggregationExceptions.RAW_CONTACT_ID1,
- AggregationExceptions.RAW_CONTACT_ID2,
- };
-
- int RAW_CONTACT_ID1 = 0;
- int RAW_CONTACT_ID2 = 1;
- }
-
- // A set of raw contact IDs for which there are aggregation exceptions
- private final HashSet<Long> mAggregationExceptionIds = new HashSet<>();
- private boolean mAggregationExceptionIdsValid;
-
- public void invalidateAggregationExceptionCache() {
- mAggregationExceptionIdsValid = false;
- }
-
- /**
- * Finds all raw contact IDs for which there are aggregation exceptions. The list of
- * ids is used as an optimization in aggregation: there is no point to run a query against
- * the agg_exceptions table if it is known that there are no records there for a given
- * raw contact ID.
- */
- private void prefetchAggregationExceptionIds(SQLiteDatabase db) {
- mAggregationExceptionIds.clear();
- final Cursor c = db.query(AggregateExceptionPrefetchQuery.TABLE,
- AggregateExceptionPrefetchQuery.COLUMNS,
- null, null, null, null, null);
-
- try {
- while (c.moveToNext()) {
- long rawContactId1 = c.getLong(AggregateExceptionPrefetchQuery.RAW_CONTACT_ID1);
- long rawContactId2 = c.getLong(AggregateExceptionPrefetchQuery.RAW_CONTACT_ID2);
- mAggregationExceptionIds.add(rawContactId1);
- mAggregationExceptionIds.add(rawContactId2);
- }
- } finally {
- c.close();
- }
-
- mAggregationExceptionIdsValid = true;
- }
-
- interface AggregateExceptionQuery {
- String TABLE = Tables.AGGREGATION_EXCEPTIONS
- + " JOIN raw_contacts raw_contacts1 "
- + " ON (agg_exceptions.raw_contact_id1 = raw_contacts1._id) "
- + " JOIN raw_contacts raw_contacts2 "
- + " ON (agg_exceptions.raw_contact_id2 = raw_contacts2._id) ";
-
- String[] COLUMNS = {
- AggregationExceptions.TYPE,
- AggregationExceptions.RAW_CONTACT_ID1,
- "raw_contacts1." + RawContacts.CONTACT_ID,
- "raw_contacts1." + RawContactsColumns.ACCOUNT_ID,
- "raw_contacts1." + RawContactsColumns.AGGREGATION_NEEDED,
- AggregationExceptions.RAW_CONTACT_ID2,
- "raw_contacts2." + RawContacts.CONTACT_ID,
- "raw_contacts2." + RawContactsColumns.ACCOUNT_ID,
- "raw_contacts2." + RawContactsColumns.AGGREGATION_NEEDED,
- };
-
- int TYPE = 0;
- int RAW_CONTACT_ID1 = 1;
- int CONTACT_ID1 = 2;
- int ACCOUNT_ID1 = 3;
- int AGGREGATION_NEEDED_1 = 4;
- int RAW_CONTACT_ID2 = 5;
- int CONTACT_ID2 = 6;
- int ACCOUNT_ID2 = 7;
- int AGGREGATION_NEEDED_2 = 8;
- }
-
- /**
* Computes match scores based on exceptions entered by the user: always match and never match.
*/
private void updateMatchScoresBasedOnExceptions(SQLiteDatabase db, long rawContactId,
@@ -1422,69 +553,6 @@ public class ContactAggregator2 {
}
}
-
- private interface NameLookupQuery {
- String TABLE = Tables.NAME_LOOKUP;
-
- String SELECTION = NameLookupColumns.RAW_CONTACT_ID + "=?";
- String SELECTION_STRUCTURED_NAME_BASED =
- SELECTION + " AND " + STRUCTURED_NAME_BASED_LOOKUP_SQL;
-
- String[] COLUMNS = new String[] {
- NameLookupColumns.NORMALIZED_NAME,
- NameLookupColumns.NAME_TYPE
- };
-
- int NORMALIZED_NAME = 0;
- int NAME_TYPE = 1;
- }
-
- private void loadNameMatchCandidates(SQLiteDatabase db, long rawContactId,
- MatchCandidateList candidates, boolean structuredNameBased) {
- candidates.clear();
- mSelectionArgs1[0] = String.valueOf(rawContactId);
- Cursor c = db.query(NameLookupQuery.TABLE, NameLookupQuery.COLUMNS,
- structuredNameBased
- ? NameLookupQuery.SELECTION_STRUCTURED_NAME_BASED
- : NameLookupQuery.SELECTION,
- mSelectionArgs1, null, null, null);
- try {
- while (c.moveToNext()) {
- String normalizedName = c.getString(NameLookupQuery.NORMALIZED_NAME);
- int type = c.getInt(NameLookupQuery.NAME_TYPE);
- candidates.add(normalizedName, type);
- }
- } finally {
- c.close();
- }
- }
-
- private interface IdentityLookupMatchQuery {
- final String TABLE = Tables.DATA + " dataA"
- + " JOIN " + Tables.DATA + " dataB" +
- " ON (dataA." + Identity.NAMESPACE + "=dataB." + Identity.NAMESPACE +
- " AND dataA." + Identity.IDENTITY + "=dataB." + Identity.IDENTITY + ")"
- + " JOIN " + Tables.RAW_CONTACTS +
- " ON (dataB." + Data.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- final String SELECTION = "dataA." + Data.RAW_CONTACT_ID + "=?1"
- + " AND dataA." + DataColumns.MIMETYPE_ID + "=?2"
- + " AND dataA." + Identity.NAMESPACE + " NOT NULL"
- + " AND dataA." + Identity.IDENTITY + " NOT NULL"
- + " AND dataB." + DataColumns.MIMETYPE_ID + "=?2"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
- + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
-
- final String[] COLUMNS = new String[] {
- RawContacts._ID, RawContacts.CONTACT_ID, RawContactsColumns.ACCOUNT_ID
- };
-
- int RAW_CONTACT_ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
- }
-
/**
* Finds contacts with exact identity matches to the the specified raw contact.
*/
@@ -1505,37 +573,6 @@ public class ContactAggregator2 {
} finally {
c.close();
}
-
- }
-
- private interface NameLookupMatchQuery {
- String TABLE = Tables.NAME_LOOKUP + " nameA"
- + " JOIN " + Tables.NAME_LOOKUP + " nameB" +
- " ON (" + "nameA." + NameLookupColumns.NORMALIZED_NAME + "="
- + "nameB." + NameLookupColumns.NORMALIZED_NAME + ")"
- + " JOIN " + Tables.RAW_CONTACTS +
- " ON (nameB." + NameLookupColumns.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String SELECTION = "nameA." + NameLookupColumns.RAW_CONTACT_ID + "=?"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
- + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
-
- String[] COLUMNS = new String[] {
- RawContacts._ID,
- RawContacts.CONTACT_ID,
- RawContactsColumns.ACCOUNT_ID,
- "nameA." + NameLookupColumns.NORMALIZED_NAME,
- "nameA." + NameLookupColumns.NAME_TYPE,
- "nameB." + NameLookupColumns.NAME_TYPE,
- };
-
- int RAW_CONTACT_ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
- int NAME = 3;
- int NAME_TYPE_A = 4;
- int NAME_TYPE_B = 5;
}
/**
@@ -1567,70 +604,22 @@ public class ContactAggregator2 {
}
}
- private interface NameLookupMatchQueryWithParameter {
- String TABLE = Tables.NAME_LOOKUP
- + " JOIN " + Tables.RAW_CONTACTS +
- " ON (" + NameLookupColumns.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String[] COLUMNS = new String[] {
- RawContacts._ID,
- RawContacts.CONTACT_ID,
- RawContactsColumns.ACCOUNT_ID,
- NameLookupColumns.NORMALIZED_NAME,
- NameLookupColumns.NAME_TYPE,
- };
-
- int RAW_CONTACT_ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
- int NAME = 3;
- int NAME_TYPE = 4;
- }
-
- private final class NameLookupSelectionBuilder extends NameLookupBuilder {
-
- private final MatchCandidateList mNameLookupCandidates;
-
- private StringBuilder mSelection = new StringBuilder(
- NameLookupColumns.NORMALIZED_NAME + " IN(");
-
-
- public NameLookupSelectionBuilder(NameSplitter splitter, MatchCandidateList candidates) {
- super(splitter);
- this.mNameLookupCandidates = candidates;
- }
-
- @Override
- protected String[] getCommonNicknameClusters(String normalizedName) {
- return mCommonNicknameCache.getCommonNicknameClusters(normalizedName);
- }
-
- @Override
- protected void insertNameLookup(
- long rawContactId, long dataId, int lookupType, String string) {
- mNameLookupCandidates.add(string, lookupType);
- DatabaseUtils.appendEscapedSQLString(mSelection, string);
- mSelection.append(',');
- }
-
- public boolean isEmpty() {
- return mNameLookupCandidates.isEmpty();
- }
-
- public String getSelection() {
- mSelection.setLength(mSelection.length() - 1); // Strip last comma
- mSelection.append(')');
- return mSelection.toString();
- }
-
- public int getLookupType(String name) {
- for (int i = 0; i < mNameLookupCandidates.mCount; i++) {
- if (mNameLookupCandidates.mList.get(i).mName.equals(name)) {
- return mNameLookupCandidates.mList.get(i).mLookupType;
- }
+ private void updateMatchScoresBasedOnEmailMatches(SQLiteDatabase db, long rawContactId,
+ RawContactMatcher matcher) {
+ mSelectionArgs2[0] = String.valueOf(rawContactId);
+ mSelectionArgs2[1] = String.valueOf(mMimeTypeIdEmail);
+ Cursor c = db.query(EmailLookupQuery.TABLE, EmailLookupQuery.COLUMNS,
+ EmailLookupQuery.SELECTION,
+ mSelectionArgs2, null, null, null, SECONDARY_HIT_LIMIT_STRING);
+ try {
+ while (c.moveToNext()) {
+ long rId = c.getLong(EmailLookupQuery.RAW_CONTACT_ID);
+ long contactId = c.getLong(EmailLookupQuery.CONTACT_ID);
+ long accountId = c.getLong(EmailLookupQuery.ACCOUNT_ID);
+ matcher.updateScoreWithEmailMatch(rId, contactId, accountId);
}
- throw new IllegalStateException();
+ } finally {
+ c.close();
}
}
@@ -1669,81 +658,6 @@ public class ContactAggregator2 {
}
}
- private interface EmailLookupQuery {
- String TABLE = Tables.DATA + " dataA"
- + " JOIN " + Tables.DATA + " dataB" +
- " ON lower(" + "dataA." + Email.DATA + ")=lower(dataB." + Email.DATA + ")"
- + " JOIN " + Tables.RAW_CONTACTS +
- " ON (dataB." + Data.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String SELECTION = "dataA." + Data.RAW_CONTACT_ID + "=?1"
- + " AND dataA." + DataColumns.MIMETYPE_ID + "=?2"
- + " AND dataA." + Email.DATA + " NOT NULL"
- + " AND dataB." + DataColumns.MIMETYPE_ID + "=?2"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
- + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
-
- String[] COLUMNS = new String[] {
- RawContacts._ID,
- RawContacts.CONTACT_ID,
- RawContactsColumns.ACCOUNT_ID
- };
-
- int RAW_CONTACT_ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
- }
-
- private void updateMatchScoresBasedOnEmailMatches(SQLiteDatabase db, long rawContactId,
- RawContactMatcher matcher) {
- mSelectionArgs2[0] = String.valueOf(rawContactId);
- mSelectionArgs2[1] = String.valueOf(mMimeTypeIdEmail);
- Cursor c = db.query(EmailLookupQuery.TABLE, EmailLookupQuery.COLUMNS,
- EmailLookupQuery.SELECTION,
- mSelectionArgs2, null, null, null, SECONDARY_HIT_LIMIT_STRING);
- try {
- while (c.moveToNext()) {
- long rId = c.getLong(EmailLookupQuery.RAW_CONTACT_ID);
- long contactId = c.getLong(EmailLookupQuery.CONTACT_ID);
- long accountId = c.getLong(EmailLookupQuery.ACCOUNT_ID);
- matcher.updateScoreWithEmailMatch(rId, contactId, accountId);
- }
- } finally {
- c.close();
- }
- }
-
- private interface PhoneLookupQuery {
- String TABLE = Tables.PHONE_LOOKUP + " phoneA"
- + " JOIN " + Tables.DATA + " dataA"
- + " ON (dataA." + Data._ID + "=phoneA." + PhoneLookupColumns.DATA_ID + ")"
- + " JOIN " + Tables.PHONE_LOOKUP + " phoneB"
- + " ON (phoneA." + PhoneLookupColumns.MIN_MATCH + "="
- + "phoneB." + PhoneLookupColumns.MIN_MATCH + ")"
- + " JOIN " + Tables.DATA + " dataB"
- + " ON (dataB." + Data._ID + "=phoneB." + PhoneLookupColumns.DATA_ID + ")"
- + " JOIN " + Tables.RAW_CONTACTS
- + " ON (dataB." + Data.RAW_CONTACT_ID + " = "
- + Tables.RAW_CONTACTS + "." + RawContacts._ID + ")";
-
- String SELECTION = "dataA." + Data.RAW_CONTACT_ID + "=?"
- + " AND PHONE_NUMBERS_EQUAL(dataA." + Phone.NUMBER + ", "
- + "dataB." + Phone.NUMBER + ",?)"
- + " AND " + RawContactsColumns.AGGREGATION_NEEDED + "=0"
- + " AND " + RawContacts.CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY;
-
- String[] COLUMNS = new String[] {
- RawContacts._ID,
- RawContacts.CONTACT_ID,
- RawContactsColumns.ACCOUNT_ID
- };
-
- int RAW_CONTACT_ID = 0;
- int CONTACT_ID = 1;
- int ACCOUNT_ID = 2;
- }
-
private void updateMatchScoresBasedOnPhoneMatches(SQLiteDatabase db, long rawContactId,
RawContactMatcher matcher) {
mSelectionArgs2[0] = String.valueOf(rawContactId);
@@ -1840,435 +754,6 @@ public class ContactAggregator2 {
}
}
- private interface RawContactsQuery {
- String SQL_FORMAT_HAS_SUPER_PRIMARY_NAME =
- " EXISTS(SELECT 1 " +
- " FROM " + Tables.DATA + " d " +
- " WHERE d." + DataColumns.MIMETYPE_ID + "=%d " +
- " AND d." + Data.RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID +
- " AND d." + Data.IS_SUPER_PRIMARY + "=1)";
-
- String SQL_FORMAT =
- "SELECT "
- + RawContactsColumns.CONCRETE_ID + ","
- + RawContactsColumns.DISPLAY_NAME + ","
- + RawContactsColumns.DISPLAY_NAME_SOURCE + ","
- + AccountsColumns.CONCRETE_ACCOUNT_TYPE + ","
- + AccountsColumns.CONCRETE_ACCOUNT_NAME + ","
- + AccountsColumns.CONCRETE_DATA_SET + ","
- + RawContacts.SOURCE_ID + ","
- + RawContacts.CUSTOM_RINGTONE + ","
- + RawContacts.SEND_TO_VOICEMAIL + ","
- + RawContacts.LAST_TIME_CONTACTED + ","
- + RawContacts.TIMES_CONTACTED + ","
- + RawContacts.STARRED + ","
- + RawContacts.PINNED + ","
- + DataColumns.CONCRETE_ID + ","
- + DataColumns.CONCRETE_MIMETYPE_ID + ","
- + Data.IS_SUPER_PRIMARY + ","
- + Photo.PHOTO_FILE_ID + ","
- + SQL_FORMAT_HAS_SUPER_PRIMARY_NAME +
- " FROM " + Tables.RAW_CONTACTS +
- " JOIN " + Tables.ACCOUNTS + " ON ("
- + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
- + ")" +
- " LEFT OUTER JOIN " + Tables.DATA +
- " ON (" + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
- + " AND ((" + DataColumns.MIMETYPE_ID + "=%d"
- + " AND " + Photo.PHOTO + " NOT NULL)"
- + " OR (" + DataColumns.MIMETYPE_ID + "=%d"
- + " AND " + Phone.NUMBER + " NOT NULL)))";
-
- String SQL_FORMAT_BY_RAW_CONTACT_ID = SQL_FORMAT +
- " WHERE " + RawContactsColumns.CONCRETE_ID + "=?";
-
- String SQL_FORMAT_BY_CONTACT_ID = SQL_FORMAT +
- " WHERE " + RawContacts.CONTACT_ID + "=?"
- + " AND " + RawContacts.DELETED + "=0";
-
- int RAW_CONTACT_ID = 0;
- int DISPLAY_NAME = 1;
- int DISPLAY_NAME_SOURCE = 2;
- int ACCOUNT_TYPE = 3;
- int ACCOUNT_NAME = 4;
- int DATA_SET = 5;
- int SOURCE_ID = 6;
- int CUSTOM_RINGTONE = 7;
- int SEND_TO_VOICEMAIL = 8;
- int LAST_TIME_CONTACTED = 9;
- int TIMES_CONTACTED = 10;
- int STARRED = 11;
- int PINNED = 12;
- int DATA_ID = 13;
- int MIMETYPE_ID = 14;
- int IS_SUPER_PRIMARY = 15;
- int PHOTO_FILE_ID = 16;
- int HAS_SUPER_PRIMARY_NAME = 17;
- }
-
- private interface ContactReplaceSqlStatement {
- String UPDATE_SQL =
- "UPDATE " + Tables.CONTACTS +
- " SET "
- + Contacts.NAME_RAW_CONTACT_ID + "=?, "
- + Contacts.PHOTO_ID + "=?, "
- + Contacts.PHOTO_FILE_ID + "=?, "
- + Contacts.SEND_TO_VOICEMAIL + "=?, "
- + Contacts.CUSTOM_RINGTONE + "=?, "
- + Contacts.LAST_TIME_CONTACTED + "=?, "
- + Contacts.TIMES_CONTACTED + "=?, "
- + Contacts.STARRED + "=?, "
- + Contacts.PINNED + "=?, "
- + Contacts.HAS_PHONE_NUMBER + "=?, "
- + Contacts.LOOKUP_KEY + "=?, "
- + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP + "=? " +
- " WHERE " + Contacts._ID + "=?";
-
- String INSERT_SQL =
- "INSERT INTO " + Tables.CONTACTS + " ("
- + Contacts.NAME_RAW_CONTACT_ID + ", "
- + Contacts.PHOTO_ID + ", "
- + Contacts.PHOTO_FILE_ID + ", "
- + Contacts.SEND_TO_VOICEMAIL + ", "
- + Contacts.CUSTOM_RINGTONE + ", "
- + Contacts.LAST_TIME_CONTACTED + ", "
- + Contacts.TIMES_CONTACTED + ", "
- + Contacts.STARRED + ", "
- + Contacts.PINNED + ", "
- + Contacts.HAS_PHONE_NUMBER + ", "
- + Contacts.LOOKUP_KEY + ", "
- + Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
- + ") " +
- " VALUES (?,?,?,?,?,?,?,?,?,?,?,?)";
-
- int NAME_RAW_CONTACT_ID = 1;
- int PHOTO_ID = 2;
- int PHOTO_FILE_ID = 3;
- int SEND_TO_VOICEMAIL = 4;
- int CUSTOM_RINGTONE = 5;
- int LAST_TIME_CONTACTED = 6;
- int TIMES_CONTACTED = 7;
- int STARRED = 8;
- int PINNED = 9;
- int HAS_PHONE_NUMBER = 10;
- int LOOKUP_KEY = 11;
- int CONTACT_LAST_UPDATED_TIMESTAMP = 12;
- int CONTACT_ID = 13;
- }
-
- /**
- * Computes aggregate-level data for the specified aggregate contact ID.
- */
- private void computeAggregateData(SQLiteDatabase db, long contactId,
- SQLiteStatement statement) {
- mSelectionArgs1[0] = String.valueOf(contactId);
- computeAggregateData(db, mRawContactsQueryByContactId, mSelectionArgs1, statement);
- }
-
- /**
- * Indicates whether the given photo entry and priority gives this photo a higher overall
- * priority than the current best photo entry and priority.
- */
- private boolean hasHigherPhotoPriority(PhotoEntry photoEntry, int priority,
- PhotoEntry bestPhotoEntry, int bestPriority) {
- int photoComparison = photoEntry.compareTo(bestPhotoEntry);
- return photoComparison < 0 || photoComparison == 0 && priority > bestPriority;
- }
-
- /**
- * Computes aggregate-level data from constituent raw contacts.
- */
- private void computeAggregateData(final SQLiteDatabase db, String sql, String[] sqlArgs,
- SQLiteStatement statement) {
- long currentRawContactId = -1;
- long bestPhotoId = -1;
- long bestPhotoFileId = 0;
- PhotoEntry bestPhotoEntry = null;
- boolean foundSuperPrimaryPhoto = false;
- int photoPriority = -1;
- int totalRowCount = 0;
- int contactSendToVoicemail = 0;
- String contactCustomRingtone = null;
- long contactLastTimeContacted = 0;
- int contactTimesContacted = 0;
- int contactStarred = 0;
- int contactPinned = Integer.MAX_VALUE;
- int hasPhoneNumber = 0;
- StringBuilder lookupKey = new StringBuilder();
-
- mDisplayNameCandidate.clear();
-
- Cursor c = db.rawQuery(sql, sqlArgs);
- try {
- while (c.moveToNext()) {
- long rawContactId = c.getLong(RawContactsQuery.RAW_CONTACT_ID);
- if (rawContactId != currentRawContactId) {
- currentRawContactId = rawContactId;
- totalRowCount++;
-
- // Assemble sub-account.
- String accountType = c.getString(RawContactsQuery.ACCOUNT_TYPE);
- String dataSet = c.getString(RawContactsQuery.DATA_SET);
- String accountWithDataSet = (!TextUtils.isEmpty(dataSet))
- ? accountType + "/" + dataSet
- : accountType;
-
- // Display name
- String displayName = c.getString(RawContactsQuery.DISPLAY_NAME);
- int displayNameSource = c.getInt(RawContactsQuery.DISPLAY_NAME_SOURCE);
- int isNameSuperPrimary = c.getInt(RawContactsQuery.HAS_SUPER_PRIMARY_NAME);
- processDisplayNameCandidate(rawContactId, displayName, displayNameSource,
- mContactsProvider.isWritableAccountWithDataSet(accountWithDataSet),
- isNameSuperPrimary != 0);
-
- // Contact options
- if (!c.isNull(RawContactsQuery.SEND_TO_VOICEMAIL)) {
- boolean sendToVoicemail =
- (c.getInt(RawContactsQuery.SEND_TO_VOICEMAIL) != 0);
- if (sendToVoicemail) {
- contactSendToVoicemail++;
- }
- }
-
- if (contactCustomRingtone == null
- && !c.isNull(RawContactsQuery.CUSTOM_RINGTONE)) {
- contactCustomRingtone = c.getString(RawContactsQuery.CUSTOM_RINGTONE);
- }
-
- long lastTimeContacted = c.getLong(RawContactsQuery.LAST_TIME_CONTACTED);
- if (lastTimeContacted > contactLastTimeContacted) {
- contactLastTimeContacted = lastTimeContacted;
- }
-
- int timesContacted = c.getInt(RawContactsQuery.TIMES_CONTACTED);
- if (timesContacted > contactTimesContacted) {
- contactTimesContacted = timesContacted;
- }
-
- if (c.getInt(RawContactsQuery.STARRED) != 0) {
- contactStarred = 1;
- }
-
- // contactPinned should be the lowest value of its constituent raw contacts,
- // excluding negative integers
- final int rawContactPinned = c.getInt(RawContactsQuery.PINNED);
- if (rawContactPinned > PinnedPositions.UNPINNED) {
- contactPinned = Math.min(contactPinned, rawContactPinned);
- }
-
- appendLookupKey(
- lookupKey,
- accountWithDataSet,
- c.getString(RawContactsQuery.ACCOUNT_NAME),
- rawContactId,
- c.getString(RawContactsQuery.SOURCE_ID),
- displayName);
- }
-
- if (!c.isNull(RawContactsQuery.DATA_ID)) {
- long dataId = c.getLong(RawContactsQuery.DATA_ID);
- long photoFileId = c.getLong(RawContactsQuery.PHOTO_FILE_ID);
- int mimetypeId = c.getInt(RawContactsQuery.MIMETYPE_ID);
- boolean superPrimary = c.getInt(RawContactsQuery.IS_SUPER_PRIMARY) != 0;
- if (mimetypeId == mMimeTypeIdPhoto) {
- if (!foundSuperPrimaryPhoto) {
- // Lookup the metadata for the photo, if available. Note that data set
- // does not come into play here, since accounts are looked up in the
- // account manager in the priority resolver.
- PhotoEntry photoEntry = getPhotoMetadata(db, photoFileId);
- String accountType = c.getString(RawContactsQuery.ACCOUNT_TYPE);
- int priority = mPhotoPriorityResolver.getPhotoPriority(accountType);
- if (superPrimary || hasHigherPhotoPriority(
- photoEntry, priority, bestPhotoEntry, photoPriority)) {
- bestPhotoEntry = photoEntry;
- photoPriority = priority;
- bestPhotoId = dataId;
- bestPhotoFileId = photoFileId;
- foundSuperPrimaryPhoto |= superPrimary;
- }
- }
- } else if (mimetypeId == mMimeTypeIdPhone) {
- hasPhoneNumber = 1;
- }
- }
- }
- } finally {
- c.close();
- }
-
- if (contactPinned == Integer.MAX_VALUE) {
- contactPinned = PinnedPositions.UNPINNED;
- }
-
- statement.bindLong(ContactReplaceSqlStatement.NAME_RAW_CONTACT_ID,
- mDisplayNameCandidate.rawContactId);
-
- if (bestPhotoId != -1) {
- statement.bindLong(ContactReplaceSqlStatement.PHOTO_ID, bestPhotoId);
- } else {
- statement.bindNull(ContactReplaceSqlStatement.PHOTO_ID);
- }
-
- if (bestPhotoFileId != 0) {
- statement.bindLong(ContactReplaceSqlStatement.PHOTO_FILE_ID, bestPhotoFileId);
- } else {
- statement.bindNull(ContactReplaceSqlStatement.PHOTO_FILE_ID);
- }
-
- statement.bindLong(ContactReplaceSqlStatement.SEND_TO_VOICEMAIL,
- totalRowCount == contactSendToVoicemail ? 1 : 0);
- DatabaseUtils.bindObjectToProgram(statement, ContactReplaceSqlStatement.CUSTOM_RINGTONE,
- contactCustomRingtone);
- statement.bindLong(ContactReplaceSqlStatement.LAST_TIME_CONTACTED,
- contactLastTimeContacted);
- statement.bindLong(ContactReplaceSqlStatement.TIMES_CONTACTED,
- contactTimesContacted);
- statement.bindLong(ContactReplaceSqlStatement.STARRED,
- contactStarred);
- statement.bindLong(ContactReplaceSqlStatement.PINNED,
- contactPinned);
- statement.bindLong(ContactReplaceSqlStatement.HAS_PHONE_NUMBER,
- hasPhoneNumber);
- statement.bindString(ContactReplaceSqlStatement.LOOKUP_KEY,
- Uri.encode(lookupKey.toString()));
- statement.bindLong(ContactReplaceSqlStatement.CONTACT_LAST_UPDATED_TIMESTAMP,
- Clock.getInstance().currentTimeMillis());
- }
-
- /**
- * Builds a lookup key using the given data.
- */
- protected void appendLookupKey(StringBuilder sb, String accountTypeWithDataSet,
- String accountName, long rawContactId, String sourceId, String displayName) {
- ContactLookupKey.appendToLookupKey(sb, accountTypeWithDataSet, accountName, rawContactId,
- sourceId, displayName);
- }
-
- /**
- * Uses the supplied values to determine if they represent a "better" display name
- * for the aggregate contact currently evaluated. If so, it updates
- * {@link #mDisplayNameCandidate} with the new values.
- */
- private void processDisplayNameCandidate(long rawContactId, String displayName,
- int displayNameSource, boolean writableAccount, boolean isNameSuperPrimary) {
-
- boolean replace = false;
- if (mDisplayNameCandidate.rawContactId == -1) {
- // No previous values available
- replace = true;
- } else if (!TextUtils.isEmpty(displayName)) {
- if (isNameSuperPrimary) {
- // A super primary name is better than any other name
- replace = true;
- } else if (mDisplayNameCandidate.isNameSuperPrimary == isNameSuperPrimary) {
- if (mDisplayNameCandidate.displayNameSource < displayNameSource) {
- // New values come from an superior source, e.g. structured name vs phone number
- replace = true;
- } else if (mDisplayNameCandidate.displayNameSource == displayNameSource) {
- if (!mDisplayNameCandidate.writableAccount && writableAccount) {
- replace = true;
- } else if (mDisplayNameCandidate.writableAccount == writableAccount) {
- if (NameNormalizer.compareComplexity(displayName,
- mDisplayNameCandidate.displayName) > 0) {
- // New name is more complex than the previously found one
- replace = true;
- }
- }
- }
- }
- }
-
- if (replace) {
- mDisplayNameCandidate.rawContactId = rawContactId;
- mDisplayNameCandidate.displayName = displayName;
- mDisplayNameCandidate.displayNameSource = displayNameSource;
- mDisplayNameCandidate.isNameSuperPrimary = isNameSuperPrimary;
- mDisplayNameCandidate.writableAccount = writableAccount;
- }
- }
-
- private interface PhotoIdQuery {
- final String[] COLUMNS = new String[] {
- AccountsColumns.CONCRETE_ACCOUNT_TYPE,
- DataColumns.CONCRETE_ID,
- Data.IS_SUPER_PRIMARY,
- Photo.PHOTO_FILE_ID,
- };
-
- int ACCOUNT_TYPE = 0;
- int DATA_ID = 1;
- int IS_SUPER_PRIMARY = 2;
- int PHOTO_FILE_ID = 3;
- }
-
- public void updatePhotoId(SQLiteDatabase db, long rawContactId) {
-
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- long bestPhotoId = -1;
- long bestPhotoFileId = 0;
- int photoPriority = -1;
-
- long photoMimeType = mDbHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
-
- String tables = Tables.RAW_CONTACTS
- + " JOIN " + Tables.ACCOUNTS + " ON ("
- + AccountsColumns.CONCRETE_ID + "=" + RawContactsColumns.CONCRETE_ACCOUNT_ID
- + ")"
- + " JOIN " + Tables.DATA + " ON("
- + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID
- + " AND (" + DataColumns.MIMETYPE_ID + "=" + photoMimeType + " AND "
- + Photo.PHOTO + " NOT NULL))";
-
- mSelectionArgs1[0] = String.valueOf(contactId);
- final Cursor c = db.query(tables, PhotoIdQuery.COLUMNS,
- RawContacts.CONTACT_ID + "=?", mSelectionArgs1, null, null, null);
- try {
- PhotoEntry bestPhotoEntry = null;
- while (c.moveToNext()) {
- long dataId = c.getLong(PhotoIdQuery.DATA_ID);
- long photoFileId = c.getLong(PhotoIdQuery.PHOTO_FILE_ID);
- boolean superPrimary = c.getInt(PhotoIdQuery.IS_SUPER_PRIMARY) != 0;
- PhotoEntry photoEntry = getPhotoMetadata(db, photoFileId);
-
- // Note that data set does not come into play here, since accounts are looked up in
- // the account manager in the priority resolver.
- String accountType = c.getString(PhotoIdQuery.ACCOUNT_TYPE);
- int priority = mPhotoPriorityResolver.getPhotoPriority(accountType);
- if (superPrimary || hasHigherPhotoPriority(
- photoEntry, priority, bestPhotoEntry, photoPriority)) {
- bestPhotoEntry = photoEntry;
- photoPriority = priority;
- bestPhotoId = dataId;
- bestPhotoFileId = photoFileId;
- if (superPrimary) {
- break;
- }
- }
- }
- } finally {
- c.close();
- }
-
- if (bestPhotoId == -1) {
- mPhotoIdUpdate.bindNull(1);
- } else {
- mPhotoIdUpdate.bindLong(1, bestPhotoId);
- }
-
- if (bestPhotoFileId == 0) {
- mPhotoIdUpdate.bindNull(2);
- } else {
- mPhotoIdUpdate.bindLong(2, bestPhotoFileId);
- }
-
- mPhotoIdUpdate.bindLong(3, contactId);
- mPhotoIdUpdate.execute();
- }
-
private interface PhotoFileQuery {
final String[] COLUMNS = new String[] {
PhotoFiles.HEIGHT,
@@ -2328,316 +813,12 @@ public class ContactAggregator2 {
}
return new PhotoEntry(0, 0);
}
-
- private interface DisplayNameQuery {
- String SQL_HAS_SUPER_PRIMARY_NAME =
- " EXISTS(SELECT 1 " +
- " FROM " + Tables.DATA + " d " +
- " WHERE d." + DataColumns.MIMETYPE_ID + "=? " +
- " AND d." + Data.RAW_CONTACT_ID + "=" + Views.RAW_CONTACTS
- + "." + RawContacts._ID +
- " AND d." + Data.IS_SUPER_PRIMARY + "=1)";
-
- String SQL =
- "SELECT "
- + RawContacts._ID + ","
- + RawContactsColumns.DISPLAY_NAME + ","
- + RawContactsColumns.DISPLAY_NAME_SOURCE + ","
- + SQL_HAS_SUPER_PRIMARY_NAME + ","
- + RawContacts.SOURCE_ID + ","
- + RawContacts.ACCOUNT_TYPE_AND_DATA_SET +
- " FROM " + Views.RAW_CONTACTS +
- " WHERE " + RawContacts.CONTACT_ID + "=? ";
-
- int _ID = 0;
- int DISPLAY_NAME = 1;
- int DISPLAY_NAME_SOURCE = 2;
- int HAS_SUPER_PRIMARY_NAME = 3;
- int SOURCE_ID = 4;
- int ACCOUNT_TYPE_AND_DATA_SET = 5;
- }
-
- public void updateDisplayNameForRawContact(SQLiteDatabase db, long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- updateDisplayNameForContact(db, contactId);
- }
-
- public void updateDisplayNameForContact(SQLiteDatabase db, long contactId) {
- boolean lookupKeyUpdateNeeded = false;
-
- mDisplayNameCandidate.clear();
-
- mSelectionArgs2[0] = String.valueOf(mDbHelper.getMimeTypeIdForStructuredName());
- mSelectionArgs2[1] = String.valueOf(contactId);
- final Cursor c = db.rawQuery(DisplayNameQuery.SQL, mSelectionArgs2);
- try {
- while (c.moveToNext()) {
- long rawContactId = c.getLong(DisplayNameQuery._ID);
- String displayName = c.getString(DisplayNameQuery.DISPLAY_NAME);
- int displayNameSource = c.getInt(DisplayNameQuery.DISPLAY_NAME_SOURCE);
- int isNameSuperPrimary = c.getInt(DisplayNameQuery.HAS_SUPER_PRIMARY_NAME);
- String accountTypeAndDataSet = c.getString(
- DisplayNameQuery.ACCOUNT_TYPE_AND_DATA_SET);
- processDisplayNameCandidate(rawContactId, displayName, displayNameSource,
- mContactsProvider.isWritableAccountWithDataSet(accountTypeAndDataSet),
- isNameSuperPrimary != 0);
-
- // If the raw contact has no source id, the lookup key is based on the display
- // name, so the lookup key needs to be updated.
- lookupKeyUpdateNeeded |= c.isNull(DisplayNameQuery.SOURCE_ID);
- }
- } finally {
- c.close();
- }
-
- if (mDisplayNameCandidate.rawContactId != -1) {
- mDisplayNameUpdate.bindLong(1, mDisplayNameCandidate.rawContactId);
- mDisplayNameUpdate.bindLong(2, contactId);
- mDisplayNameUpdate.execute();
- }
-
- if (lookupKeyUpdateNeeded) {
- updateLookupKeyForContact(db, contactId);
- }
- }
-
-
- /**
- * Updates the {@link Contacts#HAS_PHONE_NUMBER} flag for the aggregate contact containing the
- * specified raw contact.
- */
- public void updateHasPhoneNumber(SQLiteDatabase db, long rawContactId) {
-
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- final SQLiteStatement hasPhoneNumberUpdate = db.compileStatement(
- "UPDATE " + Tables.CONTACTS +
- " SET " + Contacts.HAS_PHONE_NUMBER + "="
- + "(SELECT (CASE WHEN COUNT(*)=0 THEN 0 ELSE 1 END)"
- + " FROM " + Tables.DATA_JOIN_RAW_CONTACTS
- + " WHERE " + DataColumns.MIMETYPE_ID + "=?"
- + " AND " + Phone.NUMBER + " NOT NULL"
- + " AND " + RawContacts.CONTACT_ID + "=?)" +
- " WHERE " + Contacts._ID + "=?");
- try {
- hasPhoneNumberUpdate.bindLong(1, mDbHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE));
- hasPhoneNumberUpdate.bindLong(2, contactId);
- hasPhoneNumberUpdate.bindLong(3, contactId);
- hasPhoneNumberUpdate.execute();
- } finally {
- hasPhoneNumberUpdate.close();
- }
- }
-
- private interface LookupKeyQuery {
- String TABLE = Views.RAW_CONTACTS;
- String[] COLUMNS = new String[] {
- RawContacts._ID,
- RawContactsColumns.DISPLAY_NAME,
- RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
- RawContacts.ACCOUNT_NAME,
- RawContacts.SOURCE_ID,
- };
-
- int ID = 0;
- int DISPLAY_NAME = 1;
- int ACCOUNT_TYPE_AND_DATA_SET = 2;
- int ACCOUNT_NAME = 3;
- int SOURCE_ID = 4;
- }
-
- public void updateLookupKeyForRawContact(SQLiteDatabase db, long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- updateLookupKeyForContact(db, contactId);
- }
-
- private void updateLookupKeyForContact(SQLiteDatabase db, long contactId) {
- String lookupKey = computeLookupKeyForContact(db, contactId);
-
- if (lookupKey == null) {
- mLookupKeyUpdate.bindNull(1);
- } else {
- mLookupKeyUpdate.bindString(1, Uri.encode(lookupKey));
- }
- mLookupKeyUpdate.bindLong(2, contactId);
-
- mLookupKeyUpdate.execute();
- }
-
- protected String computeLookupKeyForContact(SQLiteDatabase db, long contactId) {
- StringBuilder sb = new StringBuilder();
- mSelectionArgs1[0] = String.valueOf(contactId);
- final Cursor c = db.query(LookupKeyQuery.TABLE, LookupKeyQuery.COLUMNS,
- RawContacts.CONTACT_ID + "=?", mSelectionArgs1, null, null, RawContacts._ID);
- try {
- while (c.moveToNext()) {
- ContactLookupKey.appendToLookupKey(sb,
- c.getString(LookupKeyQuery.ACCOUNT_TYPE_AND_DATA_SET),
- c.getString(LookupKeyQuery.ACCOUNT_NAME),
- c.getLong(LookupKeyQuery.ID),
- c.getString(LookupKeyQuery.SOURCE_ID),
- c.getString(LookupKeyQuery.DISPLAY_NAME));
- }
- } finally {
- c.close();
- }
- return sb.length() == 0 ? null : sb.toString();
- }
-
- /**
- * Execute {@link SQLiteStatement} that will update the
- * {@link Contacts#STARRED} flag for the given {@link RawContacts#_ID}.
- */
- public void updateStarred(long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
-
- mStarredUpdate.bindLong(1, contactId);
- mStarredUpdate.execute();
- }
-
- /**
- * Execute {@link SQLiteStatement} that will update the
- * {@link Contacts#PINNED} flag for the given {@link RawContacts#_ID}.
- */
- public void updatePinned(long rawContactId) {
- long contactId = mDbHelper.getContactId(rawContactId);
- if (contactId == 0) {
- return;
- }
- mPinnedUpdate.bindLong(1, contactId);
- mPinnedUpdate.execute();
- }
-
- /**
- * Finds matching contacts and returns a cursor on those.
- */
- public Cursor queryAggregationSuggestions(SQLiteQueryBuilder qb,
- String[] projection, long contactId, int maxSuggestions, String filter,
- ArrayList<AggregationSuggestionParameter> parameters) {
- final SQLiteDatabase db = mDbHelper.getReadableDatabase();
- db.beginTransaction();
- try {
- List<MatchScore> bestMatches = findMatchingContacts(db, contactId, parameters);
- return queryMatchingContacts(qb, db, projection, bestMatches, maxSuggestions, filter);
- } finally {
- db.endTransaction();
- }
- }
-
- private interface ContactIdQuery {
- String[] COLUMNS = new String[] {
- Contacts._ID
- };
-
- int _ID = 0;
- }
-
- /**
- * Loads contacts with specified IDs and returns them in the order of IDs in the
- * supplied list.
- */
- private Cursor queryMatchingContacts(SQLiteQueryBuilder qb, SQLiteDatabase db,
- String[] projection, List<MatchScore> bestMatches, int maxSuggestions, String filter) {
- StringBuilder sb = new StringBuilder();
- sb.append(Contacts._ID);
- sb.append(" IN (");
- for (int i = 0; i < bestMatches.size(); i++) {
- MatchScore matchScore = bestMatches.get(i);
- if (i != 0) {
- sb.append(",");
- }
- sb.append(matchScore.getContactId());
- }
- sb.append(")");
-
- if (!TextUtils.isEmpty(filter)) {
- sb.append(" AND " + Contacts._ID + " IN ");
- mContactsProvider.appendContactFilterAsNestedQuery(sb, filter);
- }
-
- // Run a query and find ids of best matching contacts satisfying the filter (if any)
- HashSet<Long> foundIds = new HashSet<>();
- Cursor cursor = db.query(qb.getTables(), ContactIdQuery.COLUMNS, sb.toString(),
- null, null, null, null);
- try {
- while(cursor.moveToNext()) {
- foundIds.add(cursor.getLong(ContactIdQuery._ID));
- }
- } finally {
- cursor.close();
- }
-
- // Exclude all contacts that did not match the filter
- Iterator<MatchScore> iter = bestMatches.iterator();
- while (iter.hasNext()) {
- long id = iter.next().getContactId();
- if (!foundIds.contains(id)) {
- iter.remove();
- }
- }
-
- // Limit the number of returned suggestions
- final List<MatchScore> limitedMatches;
- if (bestMatches.size() > maxSuggestions) {
- limitedMatches = bestMatches.subList(0, maxSuggestions);
- } else {
- limitedMatches = bestMatches;
- }
-
- // Build an in-clause with the remaining contact IDs
- sb.setLength(0);
- sb.append(Contacts._ID);
- sb.append(" IN (");
- for (int i = 0; i < limitedMatches.size(); i++) {
- MatchScore matchScore = limitedMatches.get(i);
- if (i != 0) {
- sb.append(",");
- }
- sb.append(matchScore.getContactId());
- }
- sb.append(")");
-
- // Run the final query with the required projection and contact IDs found by the first query
- cursor = qb.query(db, projection, sb.toString(), null, null, null, Contacts._ID);
-
- // Build a sorted list of discovered IDs
- ArrayList<Long> sortedContactIds = new ArrayList<Long>(limitedMatches.size());
- for (MatchScore matchScore : limitedMatches) {
- sortedContactIds.add(matchScore.getContactId());
- }
-
- Collections.sort(sortedContactIds);
-
- // Map cursor indexes according to the descending order of match scores
- int[] positionMap = new int[limitedMatches.size()];
- for (int i = 0; i < positionMap.length; i++) {
- long id = limitedMatches.get(i).getContactId();
- positionMap[i] = sortedContactIds.indexOf(id);
- }
-
- return new ReorderingCursorWrapper(cursor, positionMap);
- }
-
/**
* Finds contacts with data matches and returns a list of {@link MatchScore}'s in the
* descending order of match score.
* @param parameters
*/
- private List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId,
+ protected List<MatchScore> findMatchingContacts(final SQLiteDatabase db, long contactId,
ArrayList<AggregationSuggestionParameter> parameters) {
MatchCandidateList candidates = new MatchCandidateList();
diff --git a/src/com/android/providers/contacts/aggregation/util/ContactMatcher.java b/src/com/android/providers/contacts/aggregation/util/ContactMatcher.java
index 2e552e9..9b71651 100644
--- a/src/com/android/providers/contacts/aggregation/util/ContactMatcher.java
+++ b/src/com/android/providers/contacts/aggregation/util/ContactMatcher.java
@@ -31,9 +31,6 @@ import java.util.List;
public class ContactMatcher {
private static final String TAG = "ContactMatcher";
- // Best possible match score
- public static final int MAX_SCORE = 100;
-
// Suggest to aggregate contacts if their match score is equal or greater than this threshold
public static final int SCORE_THRESHOLD_SUGGEST = 50;
@@ -59,9 +56,6 @@ public class ContactMatcher {
// Maximum number of characters in a name to be considered by the matching algorithm.
private static final int MAX_MATCHED_NAME_LENGTH = 30;
- // Scores a multiplied by this number to allow room for "fractional" scores
- private static final int SCORE_SCALE = 1000;
-
public static final int MATCHING_ALGORITHM_EXACT = 0;
public static final int MATCHING_ALGORITHM_CONSERVATIVE = 1;
public static final int MATCHING_ALGORITHM_APPROXIMATE = 2;
@@ -159,88 +153,6 @@ public class ContactMatcher {
return sMaxScore[index];
}
- /**
- * Captures the max score and match count for a specific contact. Used in an
- * contactId - MatchScore map.
- */
- public static class MatchScore implements Comparable<MatchScore> {
- private long mContactId;
- private boolean mKeepIn;
- private boolean mKeepOut;
- private int mPrimaryScore;
- private int mSecondaryScore;
- private int mMatchCount;
-
- public MatchScore(long contactId) {
- this.mContactId = contactId;
- }
-
- public void reset(long contactId) {
- this.mContactId = contactId;
- mKeepIn = false;
- mKeepOut = false;
- mPrimaryScore = 0;
- mSecondaryScore = 0;
- mMatchCount = 0;
- }
-
- public long getContactId() {
- return mContactId;
- }
-
- public void updatePrimaryScore(int score) {
- if (score > mPrimaryScore) {
- mPrimaryScore = score;
- }
- mMatchCount++;
- }
-
- public void updateSecondaryScore(int score) {
- if (score > mSecondaryScore) {
- mSecondaryScore = score;
- }
- mMatchCount++;
- }
-
- public void keepIn() {
- mKeepIn = true;
- }
-
- public void keepOut() {
- mKeepOut = true;
- }
-
- public int getScore() {
- if (mKeepOut) {
- return 0;
- }
-
- if (mKeepIn) {
- return MAX_SCORE;
- }
-
- int score = (mPrimaryScore > mSecondaryScore ? mPrimaryScore : mSecondaryScore);
-
- // Ensure that of two contacts with the same match score the one with more matching
- // data elements wins.
- return score * SCORE_SCALE + mMatchCount;
- }
-
- /**
- * Descending order of match score.
- */
- @Override
- public int compareTo(MatchScore another) {
- return another.getScore() - getScore();
- }
-
- @Override
- public String toString() {
- return mContactId + ": " + mPrimaryScore + "/" + mSecondaryScore + "(" + mMatchCount
- + ")";
- }
- }
-
private final HashMap<Long, MatchScore> mScores = new HashMap<Long, MatchScore>();
private final ArrayList<MatchScore> mScoreList = new ArrayList<MatchScore>();
private int mScoreCount = 0;
@@ -268,7 +180,7 @@ public class ContactMatcher {
* Marks the contact as a full match, because we found an Identity match
*/
public void matchIdentity(long contactId) {
- updatePrimaryScore(contactId, MAX_SCORE);
+ updatePrimaryScore(contactId, MatchScore.MAX_SCORE);
}
/**
@@ -374,18 +286,18 @@ public class ContactMatcher {
for (int i = 0; i < mScoreCount; i++) {
MatchScore score = mScoreList.get(i);
- if (score.mKeepOut) {
+ if (score.isKeepOut()) {
continue;
}
- int s = score.mSecondaryScore;
+ int s = score.getSecondaryScore();
if (s >= threshold) {
if (contactIds == null) {
contactIds = new ArrayList<Long>();
}
- contactIds.add(score.mContactId);
+ contactIds.add(score.getContactId());
}
- score.mPrimaryScore = NO_DATA_SCORE;
+ score.setPrimaryScore(NO_DATA_SCORE);
}
return contactIds;
}
@@ -401,17 +313,17 @@ public class ContactMatcher {
int maxScore = 0;
for (int i = 0; i < mScoreCount; i++) {
MatchScore score = mScoreList.get(i);
- if (score.mKeepOut) {
+ if (score.isKeepOut()) {
continue;
}
- if (score.mKeepIn) {
- return score.mContactId;
+ if (score.isKeepIn()) {
+ return score.getContactId();
}
- int s = score.mPrimaryScore;
+ int s = score.getPrimaryScore();
if (s == NO_DATA_SCORE) {
- s = score.mSecondaryScore;
+ s = score.getSecondaryScore();
}
if (s >= threshold) {
@@ -420,8 +332,8 @@ public class ContactMatcher {
}
// In order to make it stable, let's jut pick the one with the lowest ID
// if multiple candidates are found.
- if ((s > maxScore) || ((s == maxScore) && (contactId > score.mContactId))) {
- contactId = score.mContactId;
+ if ((s > maxScore) || ((s == maxScore) && (contactId > score.getContactId()))) {
+ contactId = score.getContactId();
maxScore = s;
}
}
@@ -433,7 +345,7 @@ public class ContactMatcher {
* Returns matches in the order of descending score.
*/
public List<MatchScore> pickBestMatches(int threshold) {
- int scaledThreshold = threshold * SCORE_SCALE;
+ int scaledThreshold = threshold * MatchScore.SCORE_SCALE;
List<MatchScore> matches = mScoreList.subList(0, mScoreCount);
Collections.sort(matches);
int count = 0;
diff --git a/src/com/android/providers/contacts/aggregation/util/MatchScore.java b/src/com/android/providers/contacts/aggregation/util/MatchScore.java
new file mode 100644
index 0000000..f87731c
--- /dev/null
+++ b/src/com/android/providers/contacts/aggregation/util/MatchScore.java
@@ -0,0 +1,149 @@
+/*
+ * 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.util;
+
+/**
+ * Captures the max score and match count for a specific raw contact or contact.
+ */
+public class MatchScore implements Comparable<MatchScore> {
+ // Scores a multiplied by this number to allow room for "fractional" scores
+ public static final int SCORE_SCALE = 1000;
+ // Best possible match score
+ public static final int MAX_SCORE = 100;
+
+ private long mRawContactId;
+ private long mContactId;
+ private long mAccountId;
+
+ private boolean mKeepIn;
+ private boolean mKeepOut;
+
+ private int mPrimaryScore;
+ private int mSecondaryScore;
+ private int mMatchCount;
+
+ public MatchScore(long rawContactId, long contactId, long accountId) {
+ this.mRawContactId = rawContactId;
+ this.mContactId = contactId;
+ this.mAccountId = accountId;
+ }
+
+ public MatchScore(long contactId) {
+ this.mRawContactId = 0;
+ this.mContactId = contactId;
+ this.mAccountId = 0;
+ }
+
+ public void reset(long rawContactId, long contactId, long accountId) {
+ this.mRawContactId = rawContactId;
+ this.mContactId = contactId;
+ this.mAccountId = accountId;
+ mKeepIn = false;
+ mKeepOut = false;
+ mPrimaryScore = 0;
+ mSecondaryScore = 0;
+ mMatchCount = 0;
+ }
+
+ public void reset(long contactId) {
+ this.reset(0l, contactId, 0l);
+ }
+
+
+ public long getRawContactId() {
+ return mRawContactId;
+ }
+
+ public long getContactId() {
+ return mContactId;
+ }
+
+ public long getAccountId() {
+ return mAccountId;
+ }
+
+ public void updatePrimaryScore(int score) {
+ if (score > mPrimaryScore) {
+ mPrimaryScore = score;
+ }
+ mMatchCount++;
+ }
+
+ public void updateSecondaryScore(int score) {
+ if (score > mSecondaryScore) {
+ mSecondaryScore = score;
+ }
+ mMatchCount++;
+ }
+
+ public void keepIn() {
+ mKeepIn = true;
+ }
+
+ public void keepOut() {
+ mKeepOut = true;
+ }
+
+ public int getScore() {
+ if (mKeepOut) {
+ return 0;
+ }
+
+ if (mKeepIn) {
+ return MAX_SCORE;
+ }
+
+ int score = (mPrimaryScore > mSecondaryScore ? mPrimaryScore : mSecondaryScore);
+
+ // Ensure that of two contacts with the same match score the one with more matching
+ // data elements wins.
+ return score * SCORE_SCALE + mMatchCount;
+ }
+
+ public boolean isKeepIn() {
+ return mKeepIn;
+ }
+
+ public boolean isKeepOut() {
+ return mKeepOut;
+ }
+
+ public int getPrimaryScore() {
+ return mPrimaryScore;
+ }
+
+ public int getSecondaryScore() {
+ return mSecondaryScore;
+ }
+
+ public void setPrimaryScore(int mPrimaryScore) {
+ this.mPrimaryScore = mPrimaryScore;
+ }
+
+ /**
+ * Descending order of match score.
+ */
+ @Override
+ public int compareTo(MatchScore another) {
+ return another.getScore() - getScore();
+ }
+
+ @Override
+ public String toString() {
+ return mRawContactId + "/" + mContactId + "/" + mAccountId + ": " + mPrimaryScore +
+ "/" + mSecondaryScore + "(" + mMatchCount + ")";
+ }
+}
diff --git a/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java b/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java
index 3b0150c..1abcfa1 100644
--- a/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java
+++ b/src/com/android/providers/contacts/aggregation/util/RawContactMatcher.java
@@ -15,11 +15,10 @@
*/
package com.android.providers.contacts.aggregation.util;
+import android.util.Log;
import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType;
import com.android.providers.contacts.util.Hex;
-import android.util.Log;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -160,102 +159,6 @@ public class RawContactMatcher {
return sMaxScore[index];
}
- /**
- * Captures the max score and match count for a specific raw contact. Used in an
- * rawContactId - MatchScore map.
- */
- public static class MatchScore implements Comparable<MatchScore> {
- private long mRawContactId;
- private long mContactId;
- private long mAccountId;
- private boolean mKeepIn;
- private boolean mKeepOut;
- private int mPrimaryScore;
- private int mSecondaryScore;
- private int mMatchCount;
-
- public MatchScore(long rawContactId, long contactId, long accountId) {
- this.mRawContactId = rawContactId;
- this.mContactId = contactId;
- this.mAccountId = accountId;
- }
-
- public void reset(long rawContactId, long contactId, long accountId) {
- this.mRawContactId = rawContactId;
- this.mContactId = contactId;
- this.mAccountId = accountId;
- mKeepIn = false;
- mKeepOut = false;
- mPrimaryScore = 0;
- mSecondaryScore = 0;
- mMatchCount = 0;
- }
-
- public long getRawContactId() {
- return mRawContactId;
- }
-
- public long getContactId() {
- return mContactId;
- }
-
- public long getAccountId() {
- return mAccountId;
- }
-
- public void updatePrimaryScore(int score) {
- if (score > mPrimaryScore) {
- mPrimaryScore = score;
- }
- mMatchCount++;
- }
-
- public void updateSecondaryScore(int score) {
- if (score > mSecondaryScore) {
- mSecondaryScore = score;
- }
- mMatchCount++;
- }
-
- public void keepIn() {
- mKeepIn = true;
- }
-
- public void keepOut() {
- mKeepOut = true;
- }
-
- public int getScore() {
- if (mKeepOut) {
- return 0;
- }
-
- if (mKeepIn) {
- return MAX_SCORE;
- }
-
- int score = (mPrimaryScore > mSecondaryScore ? mPrimaryScore : mSecondaryScore);
-
- // Ensure that of two contacts with the same match score the one with more matching
- // data elements wins.
- return score * SCORE_SCALE + mMatchCount;
- }
-
- /**
- * Descending order of match score.
- */
- @Override
- public int compareTo(MatchScore another) {
- return another.getScore() - getScore();
- }
-
- @Override
- public String toString() {
- return mRawContactId + "/" + mContactId + "/" + mAccountId + ": " + mPrimaryScore +
- "/" + mSecondaryScore + "(" + mMatchCount + ")";
- }
- }
-
private final HashMap<Long, MatchScore> mScores = new HashMap<Long, MatchScore>();
private final ArrayList<MatchScore> mScoreList = new ArrayList<MatchScore>();
private int mScoreCount = 0;
diff --git a/src/com/android/providers/contacts/aggregation/util/RawContactMatchingCandidates.java b/src/com/android/providers/contacts/aggregation/util/RawContactMatchingCandidates.java
index 39125b4..917c810 100644
--- a/src/com/android/providers/contacts/aggregation/util/RawContactMatchingCandidates.java
+++ b/src/com/android/providers/contacts/aggregation/util/RawContactMatchingCandidates.java
@@ -29,25 +29,25 @@ import static com.android.internal.util.Preconditions.checkNotNull;
* Matching candidates for a raw contact, used in the contact aggregator.
*/
public class RawContactMatchingCandidates {
- private List<RawContactMatcher.MatchScore> mBestMatches;
+ private List<MatchScore> mBestMatches;
private Set<Long> mRawContactIds = null;
private Map<Long, Long> mRawContactToContact = null;
private Map<Long, Long> mRawContactToAccount = null;
- public RawContactMatchingCandidates(List<RawContactMatcher.MatchScore> mBestMatches) {
+ public RawContactMatchingCandidates(List<MatchScore> mBestMatches) {
checkNotNull(mBestMatches);
this.mBestMatches = mBestMatches;
}
public RawContactMatchingCandidates() {
- mBestMatches = new ArrayList<RawContactMatcher.MatchScore>();
+ mBestMatches = new ArrayList<MatchScore>();
}
public int getCount() {
return mBestMatches.size();
}
- public void add(RawContactMatcher.MatchScore score) {
+ public void add(MatchScore score) {
mBestMatches.add(score);
if (mRawContactIds != null) {
mRawContactIds.add(score.getRawContactId());