diff options
author | Dmitri Plotnikov <dplotnikov@google.com> | 2009-09-20 16:56:40 -0700 |
---|---|---|
committer | Dmitri Plotnikov <dplotnikov@google.com> | 2009-09-21 09:49:52 -0700 |
commit | f23764675b35b5262a39c79aad8e9842460274b2 (patch) | |
tree | 7c5da61a6713912dc6ace1777e15760169526211 /src/com/android/providers | |
parent | d15a13a9b1dfe59dcf7196c6d5d55afe7f25d2a2 (diff) | |
download | packages_providers_ContactsProvider-f23764675b35b5262a39c79aad8e9842460274b2.zip packages_providers_ContactsProvider-f23764675b35b5262a39c79aad8e9842460274b2.tar.gz packages_providers_ContactsProvider-f23764675b35b5262a39c79aad8e9842460274b2.tar.bz2 |
No longer relying on the components of structured name for aggregation.
Now parsing display name into tokens and allowing permutations of those.
Bug IDs: 2132657, 2132636, 2089893
Change-Id: Idea256bbec3b82fb229199c6bd6e9d7b145ab075
Diffstat (limited to 'src/com/android/providers')
9 files changed, 483 insertions, 357 deletions
diff --git a/src/com/android/providers/contacts/ContactAggregator.java b/src/com/android/providers/contacts/ContactAggregator.java index e16c615..27295f4 100644 --- a/src/com/android/providers/contacts/ContactAggregator.java +++ b/src/com/android/providers/contacts/ContactAggregator.java @@ -54,7 +54,6 @@ import android.util.Log; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -70,40 +69,46 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator private static final String TAG = "ContactAggregator"; - // Data mime types used in the contact matching algorithm - private static final String MIMETYPE_SELECTION_IN_CLAUSE = MimetypesColumns.MIMETYPE + " IN ('" - + Email.CONTENT_ITEM_TYPE + "','" - + Nickname.CONTENT_ITEM_TYPE + "','" - + Phone.CONTENT_ITEM_TYPE + "','" - + StructuredName.CONTENT_ITEM_TYPE + "')"; + private interface DataMimetypeQuery { - private static final String[] DATA_JOIN_MIMETYPE_COLUMNS = new String[] { - MimetypesColumns.MIMETYPE, - Data.DATA1, - Data.DATA2 - }; + // Data mime types used in the contact matching algorithm + String MIMETYPE_SELECTION_IN_CLAUSE = MimetypesColumns.MIMETYPE + " IN ('" + + Email.CONTENT_ITEM_TYPE + "','" + + Nickname.CONTENT_ITEM_TYPE + "','" + + Phone.CONTENT_ITEM_TYPE + "','" + + StructuredName.CONTENT_ITEM_TYPE + "')"; - private static final int COL_MIMETYPE = 0; - private static final int COL_DATA1 = 1; - private static final int COL_DATA2 = 2; + String[] COLUMNS = new String[] { + MimetypesColumns.MIMETYPE, Data.DATA1 + }; - private static final String[] DATA_JOIN_MIMETYPE_AND_CONTACT_COLUMNS = new String[] { - Data.DATA1, Data.DATA2, RawContacts.CONTACT_ID - }; + int MIMETYPE = 0; + int DATA1 = 1; + } - private static final int COL_DATA_CONTACT_DATA1 = 0; - private static final int COL_DATA_CONTACT_DATA2 = 1; - private static final int COL_DATA_CONTACT_CONTACT_ID = 2; + private interface DataContactIdQuery { + String TABLE = Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS; - private static final String[] NAME_LOOKUP_COLUMNS = new String[] { - RawContacts.CONTACT_ID, NameLookupColumns.NORMALIZED_NAME, NameLookupColumns.NAME_TYPE - }; + String[] COLUMNS = new String[] { + Data.DATA1, RawContacts.CONTACT_ID + }; - private static final int COL_NAME_LOOKUP_CONTACT_ID = 0; - private static final int COL_NORMALIZED_NAME = 1; - private static final int COL_NAME_TYPE = 2; + int DATA1 = 0; + int CONTACT_ID = 1; + } - private static final String[] CONTACT_ID_COLUMN = new String[] { RawContacts._ID }; + private interface NameLookupQuery { + String TABLE = Tables.NAME_LOOKUP_JOIN_RAW_CONTACTS; + + String[] COLUMNS = new String[] { + RawContacts.CONTACT_ID, NameLookupColumns.NORMALIZED_NAME, + NameLookupColumns.NAME_TYPE + }; + + int CONTACT_ID = 0; + int NORMALIZED_NAME = 1; + int NAME_TYPE = 2; + } private interface EmailLookupQuery { String TABLE = Tables.DATA_JOIN_RAW_CONTACTS; @@ -115,12 +120,12 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator int CONTACT_ID = 0; } + private static final String[] CONTACT_ID_COLUMN = new String[] { RawContacts._ID }; private static final String[] CONTACT_ID_COLUMNS = new String[]{ RawContacts.CONTACT_ID }; private static final int COL_CONTACT_ID = 0; - private static final int MODE_INSERT_LOOKUP_DATA = 0; - private static final int MODE_AGGREGATION = 1; - private static final int MODE_SUGGESTIONS = 2; + private static final int MODE_AGGREGATION = 0; + private static final int MODE_SUGGESTIONS = 1; /** * When yielding the transaction to another thread, sleep for this many milliseconds @@ -209,6 +214,27 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator } } + private class AggregationNameLookupBuilder extends NameLookupBuilder { + + private final MatchCandidateList mCandidates; + + public AggregationNameLookupBuilder(NameSplitter splitter, MatchCandidateList candidates) { + super(splitter); + mCandidates = candidates; + } + + @Override + protected void insertNameLookup(long rawContactId, long dataId, int lookupType, + String name) { + mCandidates.add(name, lookupType); + } + + @Override + protected String[] getCommonNicknameClusters(String normalizedName) { + return mContactsProvider.getCommonNicknameClusters(normalizedName); + } + } + /** * Constructor. Starts a contact aggregation thread. Call {@link #quit} to kill the * aggregation thread. Call {@link #schedule} to kick off the aggregation process after @@ -752,20 +778,19 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator selection.append(") AND " + MimetypesColumns.MIMETYPE + "='" + StructuredName.CONTENT_ITEM_TYPE + "'"); - final Cursor c = db.query(Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS, - DATA_JOIN_MIMETYPE_AND_CONTACT_COLUMNS, + final Cursor c = db.query(DataContactIdQuery.TABLE, DataContactIdQuery.COLUMNS, selection.toString(), null, null, null, null); MatchCandidateList nameCandidates = new MatchCandidateList(); + AggregationNameLookupBuilder builder = + new AggregationNameLookupBuilder(mContactsProvider.getNameSplitter(), nameCandidates); try { while (c.moveToNext()) { - String givenName = c.getString(COL_DATA_CONTACT_DATA1); - String familyName = c.getString(COL_DATA_CONTACT_DATA2); - long contactId = c.getLong(COL_DATA_CONTACT_CONTACT_ID); + String name = c.getString(DataContactIdQuery.DATA1); + long contactId = c.getLong(DataContactIdQuery.CONTACT_ID); nameCandidates.clear(); - addMatchCandidatesStructuredName(givenName, familyName, MODE_INSERT_LOOKUP_DATA, - nameCandidates); + builder.insertNameLookup(0, 0, name); // Note the N^2 complexity of the following fragment. This is not a huge concern // since the number of candidates is very small and in general secondary hits @@ -800,31 +825,30 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator int mode, MatchCandidateList candidates, ContactMatcher matcher) { final Cursor c = db.query(Tables.DATA_JOIN_MIMETYPE_RAW_CONTACTS, - DATA_JOIN_MIMETYPE_COLUMNS, + DataMimetypeQuery.COLUMNS, Data.RAW_CONTACT_ID + "=" + rawContactId + " AND (" - + MIMETYPE_SELECTION_IN_CLAUSE + ")", + + DataMimetypeQuery.MIMETYPE_SELECTION_IN_CLAUSE + ")", null, null, null, null); try { while (c.moveToNext()) { - String mimeType = c.getString(COL_MIMETYPE); - String data1 = c.getString(COL_DATA1); - String data2 = c.getString(COL_DATA2); + String mimeType = c.getString(DataMimetypeQuery.MIMETYPE); + String data = c.getString(DataMimetypeQuery.DATA1); if (mimeType.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) { - addMatchCandidatesStructuredName(data1, data2, mode, candidates); + addMatchCandidatesStructuredName(data, candidates); } else if (mimeType.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) { - if (!TextUtils.isEmpty(data2)) { - addMatchCandidatesEmail(data2, mode, candidates); - lookupEmailMatches(db, data2, matcher); + if (!TextUtils.isEmpty(data)) { + addMatchCandidatesEmail(data, mode, candidates); + lookupEmailMatches(db, data, matcher); } } else if (mimeType.equals(CommonDataKinds.Phone.CONTENT_ITEM_TYPE)) { - if (!TextUtils.isEmpty(data2)) { - lookupPhoneMatches(db, data2, matcher); + if (!TextUtils.isEmpty(data)) { + lookupPhoneMatches(db, data, matcher); } } else if (mimeType.equals(CommonDataKinds.Nickname.CONTENT_ITEM_TYPE)) { - if (!TextUtils.isEmpty(data2)) { - addMatchCandidatesNickname(data2, mode, candidates); - lookupNicknameMatches(db, data2, matcher); + if (!TextUtils.isEmpty(data)) { + addMatchCandidatesNickname(data, mode, candidates); + lookupNicknameMatches(db, data, matcher); } } } @@ -840,65 +864,12 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator } /** - * Looks for matches based on the full name (first + last). + * Looks for matches based on the full name. */ - private void addMatchCandidatesStructuredName(String givenName, String familyName, int mode, - MatchCandidateList candidates) { - if (TextUtils.isEmpty(givenName)) { - - // If neither the first nor last name are specified, we won't aggregate - if (TextUtils.isEmpty(familyName)) { - return; - } - - addMatchCandidatesSingleName(familyName, candidates); - } else if (TextUtils.isEmpty(familyName)) { - addMatchCandidatesSingleName(givenName, candidates); - } else { - addMatchCandidatesFullName(givenName, familyName, mode, candidates); - } - } - - private void addMatchCandidatesSingleName(String name, MatchCandidateList candidates) { - String nameN = NameNormalizer.normalize(name); - candidates.add(nameN, NameLookupType.NAME_EXACT); - candidates.add(nameN, NameLookupType.NAME_COLLATION_KEY); - - // Take care of first and last names swapped - String[] clusters = mOpenHelper.getCommonNicknameClusters(nameN); - if (clusters != null) { - for (int i = 0; i < clusters.length; i++) { - candidates.add(clusters[i], NameLookupType.NAME_VARIANT); - } - } - } - - private void addMatchCandidatesFullName(String givenName, String familyName, int mode, - MatchCandidateList candidates) { - final String givenNameN = NameNormalizer.normalize(givenName); - final String[] givenNameNicknames = mOpenHelper.getCommonNicknameClusters(givenNameN); - final String familyNameN = NameNormalizer.normalize(familyName); - final String[] familyNameNicknames = mOpenHelper.getCommonNicknameClusters(familyNameN); - candidates.add(givenNameN + "." + familyNameN, NameLookupType.NAME_EXACT); - candidates.add(givenNameN + familyNameN, NameLookupType.NAME_COLLATION_KEY); - candidates.add(familyNameN + givenNameN, NameLookupType.NAME_COLLATION_KEY); - if (givenNameNicknames != null) { - for (int i = 0; i < givenNameNicknames.length; i++) { - candidates.add(givenNameNicknames[i] + "." + familyNameN, - NameLookupType.NAME_VARIANT); - candidates.add(familyNameN + "." + givenNameNicknames[i], - NameLookupType.NAME_VARIANT); - } - } - candidates.add(familyNameN + "." + givenNameN, NameLookupType.NAME_VARIANT); - if (familyNameNicknames != null) { - for (int i = 0; i < familyNameNicknames.length; i++) { - candidates.add(familyNameNicknames[i] + "." + givenNameN, - NameLookupType.NAME_VARIANT); - candidates.add(givenNameN + "." + familyNameNicknames[i], - NameLookupType.NAME_VARIANT); - } - } + private void addMatchCandidatesStructuredName(String name, MatchCandidateList candidates) { + AggregationNameLookupBuilder builder = + new AggregationNameLookupBuilder(mContactsProvider.getNameSplitter(), candidates); + builder.insertNameLookup(0, 0, name); } /** @@ -984,14 +955,14 @@ public class ContactAggregator implements ContactAggregationScheduler.Aggregator */ private void matchAllCandidates(SQLiteDatabase db, String selection, MatchCandidateList candidates, ContactMatcher matcher, int algorithm) { - final Cursor c = db.query(Tables.NAME_LOOKUP_JOIN_RAW_CONTACTS, NAME_LOOKUP_COLUMNS, + final Cursor c = db.query(NameLookupQuery.TABLE, NameLookupQuery.COLUMNS, selection, null, null, null, null, String.valueOf(PRIMARY_HIT_LIMIT)); try { while (c.moveToNext()) { - Long contactId = c.getLong(COL_NAME_LOOKUP_CONTACT_ID); - String name = c.getString(COL_NORMALIZED_NAME); - int nameType = c.getInt(COL_NAME_TYPE); + Long contactId = c.getLong(NameLookupQuery.CONTACT_ID); + String name = c.getString(NameLookupQuery.NORMALIZED_NAME); + int nameType = c.getInt(NameLookupQuery.NAME_TYPE); // Determine which candidate produced this match for (int i = 0; i < candidates.mCount; i++) { diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 14ab008..f463990 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -28,6 +28,7 @@ import com.android.providers.contacts.OpenHelper.GroupsColumns; import com.android.providers.contacts.OpenHelper.MimetypesColumns; import com.android.providers.contacts.OpenHelper.NameLookupColumns; import com.android.providers.contacts.OpenHelper.NameLookupType; +import com.android.providers.contacts.OpenHelper.NicknameLookupColumns; import com.android.providers.contacts.OpenHelper.PhoneColumns; import com.android.providers.contacts.OpenHelper.PhoneLookupColumns; import com.android.providers.contacts.OpenHelper.PresenceColumns; @@ -92,12 +93,15 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import android.text.util.Rfc822Token; +import android.text.util.Rfc822Tokenizer; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; +import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -235,13 +239,13 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun public static final String[] COLUMNS = new String[] { MimetypesColumns.MIMETYPE, Data.IS_PRIMARY, - Data.DATA2, + Data.DATA1, StructuredName.DISPLAY_NAME, }; public static final int MIMETYPE = 0; public static final int IS_PRIMARY = 1; - public static final int DATA2 = 2; + public static final int DATA1 = 2; public static final int DISPLAY_NAME = 3; } @@ -253,7 +257,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun MimetypesColumns.MIMETYPE, Data.RAW_CONTACT_ID, Data.IS_PRIMARY, - Data.DATA2, + Data.DATA1, }; public static final String[] COLUMNS = new String[] { @@ -261,14 +265,14 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun MimetypesColumns.MIMETYPE, Data.RAW_CONTACT_ID, Data.IS_PRIMARY, - Data.DATA2, + Data.DATA1, }; public static final int _ID = 0; public static final int MIMETYPE = 1; public static final int RAW_CONTACT_ID = 2; public static final int IS_PRIMARY = 3; - public static final int DATA2 = 4; + public static final int DATA1 = 4; } private interface DataUpdateQuery { @@ -279,6 +283,17 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun int MIMETYPE = 2; } + + private interface NicknameLookupQuery { + String TABLE = Tables.NICKNAME_LOOKUP; + + String[] COLUMNS = new String[] { + NicknameLookupColumns.CLUSTER + }; + + int CLUSTER = 0; + } + private static final HashMap<String, Integer> sDisplayNameSources; static { sDisplayNameSources = new HashMap<String, Integer>(); @@ -341,6 +356,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private SQLiteStatement mAggregatedPresenceReplace; /** Precompiled sql statement for updating an aggregated presence status */ private SQLiteStatement mAggregatedPresenceStatusUpdate; + private SQLiteStatement mNameLookupInsert; + private SQLiteStatement mNameLookupDelete; static { // Contacts URI matching table @@ -784,7 +801,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun try { while (c.moveToNext()) { long dataId = c.getLong(DataDeleteQuery._ID); - int type = c.getInt(DataDeleteQuery.DATA2); + int type = c.getInt(DataDeleteQuery.DATA1); if (primaryType == -1 || getTypeRank(type) < getTypeRank(primaryType)) { primaryId = dataId; primaryType = type; @@ -827,7 +844,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun name = c.getString(DisplayNameQuery.DISPLAY_NAME); primary = true; } else { - name = c.getString(DisplayNameQuery.DATA2); + name = c.getString(DisplayNameQuery.DATA1); primary = (c.getInt(DisplayNameQuery.IS_PRIMARY) != 0); } @@ -901,10 +918,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun long dataId = super.insert(db, rawContactId, values); - String givenName = values.getAsString(StructuredName.GIVEN_NAME); - String familyName = values.getAsString(StructuredName.FAMILY_NAME); - mOpenHelper.insertNameLookupForStructuredName(rawContactId, dataId, givenName, - familyName); + String name = values.getAsString(StructuredName.DISPLAY_NAME); + insertNameLookupForStructuredName(rawContactId, dataId, name); fixRawContactDisplayName(db, rawContactId); return dataId; } @@ -920,14 +935,10 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun super.update(db, values, c, callerIsSyncAdapter); - boolean hasGivenName = values.containsKey(StructuredName.GIVEN_NAME); - boolean hasFamilyName = values.containsKey(StructuredName.FAMILY_NAME); - if (hasGivenName || hasFamilyName) { - String givenName = augmented.getAsString(StructuredName.GIVEN_NAME); - String familyName = augmented.getAsString(StructuredName.FAMILY_NAME); - mOpenHelper.deleteNameLookup(dataId); - mOpenHelper.insertNameLookupForStructuredName(rawContactId, dataId, givenName, - familyName); + if (values.containsKey(StructuredName.DISPLAY_NAME)) { + String name = values.getAsString(StructuredName.DISPLAY_NAME); + deleteNameLookup(dataId); + insertNameLookupForStructuredName(rawContactId, dataId, name); } fixRawContactDisplayName(db, rawContactId); } @@ -939,7 +950,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun int count = super.delete(db, c); - mOpenHelper.deleteNameLookup(dataId); + deleteNameLookup(dataId); fixRawContactDisplayName(db, rawContactId); return count; } @@ -1138,7 +1149,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun long dataId = super.insert(db, rawContactId, values); fixRawContactDisplayName(db, rawContactId); - mOpenHelper.insertNameLookupForEmail(rawContactId, dataId, address); + insertNameLookupForEmail(rawContactId, dataId, address); return dataId; } @@ -1151,8 +1162,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun super.update(db, values, c, callerIsSyncAdapter); - mOpenHelper.deleteNameLookup(dataId); - mOpenHelper.insertNameLookupForEmail(rawContactId, dataId, address); + deleteNameLookup(dataId); + insertNameLookupForEmail(rawContactId, dataId, address); fixRawContactDisplayName(db, rawContactId); } @@ -1163,7 +1174,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun int count = super.delete(db, c); - mOpenHelper.deleteNameLookup(dataId); + deleteNameLookup(dataId); fixRawContactDisplayName(db, rawContactId); return count; } @@ -1193,7 +1204,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun long dataId = super.insert(db, rawContactId, values); fixRawContactDisplayName(db, rawContactId); - mOpenHelper.insertNameLookupForNickname(rawContactId, dataId, nickname); + insertNameLookupForNickname(rawContactId, dataId, nickname); return dataId; } @@ -1206,8 +1217,8 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun super.update(db, values, c, callerIsSyncAdapter); - mOpenHelper.deleteNameLookup(dataId); - mOpenHelper.insertNameLookupForNickname(rawContactId, dataId, nickname); + deleteNameLookup(dataId); + insertNameLookupForNickname(rawContactId, dataId, nickname); fixRawContactDisplayName(db, rawContactId); } @@ -1218,7 +1229,7 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun int count = super.delete(db, c); - mOpenHelper.deleteNameLookup(dataId); + deleteNameLookup(dataId); fixRawContactDisplayName(db, rawContactId); return count; } @@ -1415,6 +1426,9 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private OpenHelper mOpenHelper; private NameSplitter mNameSplitter; + private NameLookupBuilder mNameLookupBuilder; + private HashMap<String, SoftReference<String[]>> mNicknameClusterCache = + new HashMap<String, SoftReference<String[]>>(); private PostalSplitter mPostalSplitter; private ContactAggregator mContactAggregator; @@ -1509,8 +1523,16 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun context.getString(com.android.internal.R.string.common_name_suffixes), context.getString(com.android.internal.R.string.common_name_conjunctions), locale); + mNameLookupBuilder = new StructuredNameLookupBuilder(mNameSplitter); mPostalSplitter = new PostalSplitter(locale); + mNameLookupInsert = db.compileStatement("INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "(" + + NameLookupColumns.RAW_CONTACT_ID + "," + NameLookupColumns.DATA_ID + "," + + NameLookupColumns.NAME_TYPE + "," + NameLookupColumns.NORMALIZED_NAME + + ") VALUES (?,?,?,?)"); + mNameLookupDelete = db.compileStatement("DELETE FROM " + Tables.NAME_LOOKUP + " WHERE " + + NameLookupColumns.DATA_ID + "=?"); + mDataRowHandlers = new HashMap<String, DataRowHandler>(); mDataRowHandlers.put(Email.CONTENT_ITEM_TYPE, new EmailDataRowHandler()); @@ -4210,6 +4232,127 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun mSetSuperPrimaryStatement.execute(); } + public void insertNameLookupForEmail(long rawContactId, long dataId, String email) { + if (TextUtils.isEmpty(email)) { + return; + } + + Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); + if (tokens.length == 0) { + return; + } + + String address = tokens[0].getAddress(); + int at = address.indexOf('@'); + if (at != -1) { + address = address.substring(0, at); + } + + insertNameLookup(rawContactId, dataId, + NameLookupType.EMAIL_BASED_NICKNAME, NameNormalizer.normalize(address)); + } + + /** + * Normalizes the nickname and inserts it in the name lookup table. + */ + public void insertNameLookupForNickname(long rawContactId, long dataId, String nickname) { + if (TextUtils.isEmpty(nickname)) { + return; + } + + insertNameLookup(rawContactId, dataId, + NameLookupType.NICKNAME, NameNormalizer.normalize(nickname)); + } + + + public void insertNameLookupForStructuredName(long rawContactId, long dataId, String name) { + mNameLookupBuilder.insertNameLookup(rawContactId, dataId, name); + } + + /** + * Returns nickname cluster IDs or null. Maintains cache. + */ + protected String[] getCommonNicknameClusters(String normalizedName) { + SoftReference<String[]> ref; + String[] clusters = null; + synchronized (mNicknameClusterCache) { + if (mNicknameClusterCache.containsKey(normalizedName)) { + ref = mNicknameClusterCache.get(normalizedName); + if (ref == null) { + return null; + } + clusters = ref.get(); + } + } + + if (clusters == null) { + clusters = loadNicknameClusters(normalizedName); + ref = clusters == null ? null : new SoftReference<String[]>(clusters); + synchronized (mNicknameClusterCache) { + mNicknameClusterCache.put(normalizedName, ref); + } + } + return clusters; + } + + protected String[] loadNicknameClusters(String normalizedName) { + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + String[] clusters = null; + Cursor cursor = db.query(NicknameLookupQuery.TABLE, NicknameLookupQuery.COLUMNS, + NicknameLookupColumns.NAME + "=?", new String[] { normalizedName }, + null, null, null); + try { + int count = cursor.getCount(); + if (count > 0) { + clusters = new String[count]; + for (int i = 0; i < count; i++) { + cursor.moveToNext(); + clusters[i] = cursor.getString(NicknameLookupQuery.CLUSTER); + } + } + } finally { + cursor.close(); + } + return clusters; + } + + private class StructuredNameLookupBuilder extends NameLookupBuilder { + + public StructuredNameLookupBuilder(NameSplitter splitter) { + super(splitter); + } + + @Override + protected void insertNameLookup(long rawContactId, long dataId, int lookupType, + String name) { + ContactsProvider2.this.insertNameLookup(rawContactId, dataId, lookupType, name); + } + + @Override + protected String[] getCommonNicknameClusters(String normalizedName) { + return ContactsProvider2.this.getCommonNicknameClusters(normalizedName); + } + } + + /** + * Inserts a record in the {@link Tables#NAME_LOOKUP} table. + */ + public void insertNameLookup(long rawContactId, long dataId, int lookupType, String name) { + DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 1, rawContactId); + DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 2, dataId); + DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 3, lookupType); + DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 4, name); + mNameLookupInsert.executeInsert(); + } + + /** + * Deletes all {@link Tables#NAME_LOOKUP} table rows associated with the specified data element. + */ + public void deleteNameLookup(long dataId) { + DatabaseUtils.bindObjectToProgram(mNameLookupDelete, 1, dataId); + mNameLookupDelete.execute(); + } + private void appendContactFilterAsNestedQuery(StringBuilder sb, String filterParam) { sb.append("(SELECT DISTINCT " + RawContacts.CONTACT_ID + " FROM " + Tables.RAW_CONTACTS + " JOIN name_lookup ON(" + RawContactsColumns.CONCRETE_ID + "=raw_contact_id)" diff --git a/src/com/android/providers/contacts/GlobalSearchSupport.java b/src/com/android/providers/contacts/GlobalSearchSupport.java index 3ae2a9a..a076d34 100644 --- a/src/com/android/providers/contacts/GlobalSearchSupport.java +++ b/src/com/android/providers/contacts/GlobalSearchSupport.java @@ -101,7 +101,9 @@ public class GlobalSearchSupport { DataColumns.CONCRETE_ID + " AS data_id", MimetypesColumns.MIMETYPE, Data.IS_SUPER_PRIMARY, - Data.DATA2, + Organization.COMPANY, + Email.DATA, + Phone.NUMBER, Contacts.PHOTO_ID, }; @@ -111,8 +113,10 @@ public class GlobalSearchSupport { public static final int DATA_ID = 3; public static final int MIMETYPE = 4; public static final int IS_SUPER_PRIMARY = 5; - public static final int DATA2 = 6; - public static final int PHOTO_ID = 7; + public static final int ORGANIZATION = 6; + public static final int EMAIL = 7; + public static final int PHONE = 8; + public static final int PHOTO_ID = 9; } private static class SearchSuggestion { @@ -330,15 +334,15 @@ public class GlobalSearchSupport { suggestion.titleIsName = true; } else if (Organization.CONTENT_ITEM_TYPE.equals(mimetype)) { if (isSuperPrimary || suggestion.organization == null) { - suggestion.organization = c.getString(SearchSuggestionQuery.DATA2); + suggestion.organization = c.getString(SearchSuggestionQuery.ORGANIZATION); } } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) { if (isSuperPrimary || suggestion.email == null) { - suggestion.email = c.getString(SearchSuggestionQuery.DATA2); + suggestion.email = c.getString(SearchSuggestionQuery.EMAIL); } } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) { if (isSuperPrimary || suggestion.phoneNumber == null) { - suggestion.phoneNumber = c.getString(SearchSuggestionQuery.DATA2); + suggestion.phoneNumber = c.getString(SearchSuggestionQuery.PHONE); } } diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java index aac2460..a1eb139 100644 --- a/src/com/android/providers/contacts/LegacyApiSupport.java +++ b/src/com/android/providers/contacts/LegacyApiSupport.java @@ -165,7 +165,7 @@ public class LegacyApiSupport { + " THEN 'custom:'||" + Tables.DATA + "." + Im.CUSTOM_PROTOCOL + " ELSE 'pre:'||" + Tables.DATA + "." + Im.PROTOCOL + " END)" - + " ELSE " + DataColumns.CONCRETE_DATA2 + + " ELSE " + Tables.DATA + "." + Email.DATA + " END)"; private static final Uri LIVE_FOLDERS_CONTACTS_URI = Uri.withAppendedPath( @@ -592,11 +592,11 @@ public class LegacyApiSupport { + " AS " + ContactMethods.KIND + ", " + DataColumns.CONCRETE_IS_PRIMARY + " AS " + ContactMethods.ISPRIMARY + ", " + - DataColumns.CONCRETE_DATA1 + Tables.DATA + "." + Email.TYPE + " AS " + ContactMethods.TYPE + ", " + CONTACT_METHOD_DATA_SQL + " AS " + ContactMethods.DATA + ", " + - DataColumns.CONCRETE_DATA3 + Tables.DATA + "." + Email.LABEL + " AS " + ContactMethods.LABEL + ", " + DataColumns.CONCRETE_DATA14 + " AS " + ContactMethods.AUX_DATA + ", " + diff --git a/src/com/android/providers/contacts/LegacyContactImporter.java b/src/com/android/providers/contacts/LegacyContactImporter.java index 633d6fb..458096d 100644 --- a/src/com/android/providers/contacts/LegacyContactImporter.java +++ b/src/com/android/providers/contacts/LegacyContactImporter.java @@ -539,13 +539,10 @@ public class LegacyContactImporter { NameSplitter.Name splitName = new NameSplitter.Name(); mNameSplitter.split(splitName, name); - String givenNames = splitName.getGivenNames(); - String familyName = splitName.getFamilyName(); - bindString(insert, StructuredNameInsert.PREFIX, splitName.getPrefix()); - bindString(insert, StructuredNameInsert.GIVEN_NAME, givenNames); + bindString(insert, StructuredNameInsert.GIVEN_NAME, splitName.getGivenNames()); bindString(insert, StructuredNameInsert.MIDDLE_NAME, splitName.getMiddleName()); - bindString(insert, StructuredNameInsert.FAMILY_NAME, familyName); + bindString(insert, StructuredNameInsert.FAMILY_NAME, splitName.getFamilyName()); bindString(insert, StructuredNameInsert.SUFFIX, splitName.getSuffix()); if (mPhoneticNameAvailable) { @@ -555,7 +552,7 @@ public class LegacyContactImporter { long dataId = insert(insert); - mOpenHelper.insertNameLookupForStructuredName(id, dataId, givenNames, familyName); + mContactsProvider.insertNameLookupForStructuredName(id, dataId, name); } private void insertNote(Cursor c, SQLiteStatement insert) { @@ -750,7 +747,7 @@ public class LegacyContactImporter { bindString(insert, EmailInsert.LABEL, c.getString(ContactMethodsQuery.LABEL)); long dataId = insert(insert); - mOpenHelper.insertNameLookupForEmail(personId, dataId, email); + mContactsProvider.insertNameLookupForEmail(personId, dataId, email); } private void insertIm(Cursor c, SQLiteStatement insert) { diff --git a/src/com/android/providers/contacts/NameLookupBuilder.java b/src/com/android/providers/contacts/NameLookupBuilder.java new file mode 100644 index 0000000..95909d0 --- /dev/null +++ b/src/com/android/providers/contacts/NameLookupBuilder.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2009 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; + +import com.android.providers.contacts.OpenHelper.NameLookupType; + +/** + * Given a full name, constructs all possible variants of the name. + */ +public abstract class NameLookupBuilder { + + private final NameSplitter mSplitter; + private String[] mNormalizedNames = new String[NameSplitter.MAX_TOKENS]; + private String[][] mNicknameClusters = new String[NameSplitter.MAX_TOKENS][]; + private StringBuilder mStringBuilder1 = new StringBuilder(); + private StringBuilder mStringBuilder2 = new StringBuilder(); + private String[] mNames = new String[NameSplitter.MAX_TOKENS]; + + public NameLookupBuilder(NameSplitter splitter) { + mSplitter = splitter; + } + + /** + * Inserts a name lookup record with the supplied column values. + */ + protected abstract void insertNameLookup(long rawContactId, long dataId, int lookupType, + String string); + + /** + * Returns common nickname cluster IDs for a given name. For example, it + * will return the same value for "Robert", "Bob" and "Rob". Some names belong to multiple + * clusters, e.g. Leo could be Leonard or Leopold. + * + * May return null. + * + * @param normalizedName A normalized first name, see {@link NameNormalizer#normalize}. + */ + protected abstract String[] getCommonNicknameClusters(String normalizedName); + + /** + * Inserts name lookup records for the given structured name. + */ + public void insertNameLookup(long rawContactId, long dataId, String name) { + int tokenCount = mSplitter.tokenize(mNames, name); + if (tokenCount == 0) { + return; + } + + for (int i = 0; i < tokenCount; i++) { + mNormalizedNames[i] = normalizeName(mNames[i]); + } + + // Phase I: insert all variants not involving nickname clusters + for (int i = 0; i < tokenCount; i++) { + mNames[i] = mNormalizedNames[i]; + mNicknameClusters[i] = getCommonNicknameClusters(mNames[i]); + } + + insertNameVariants(rawContactId, dataId, 0, tokenCount, true, true); + insertNicknamePermutations(rawContactId, dataId, 0, tokenCount); + } + + protected String normalizeName(String name) { + return NameNormalizer.normalize(name); + } + + /** + * Inserts all name variants based on permutations of tokens between + * fromIndex and toIndex + * + * @param initiallyExact true if the name without permutations is the exact + * original name + * @param buildCollationKey true if a collation key makes sense for these + * permutations (false if at least one of the tokens is a + * nickname cluster key) + */ + private void insertNameVariants(long rawContactId, long dataId, int fromIndex, int toIndex, + boolean initiallyExact, boolean buildCollationKey) { + if (fromIndex == toIndex) { + insertNameVariant(rawContactId, dataId, toIndex, + initiallyExact ? NameLookupType.NAME_EXACT : NameLookupType.NAME_VARIANT, + buildCollationKey); + return; + } + + // Swap the first token with each other token (including itself, which is a no-op) + // and recursively insert all permutations for the remaining tokens + String firstToken = mNames[fromIndex]; + for (int i = fromIndex; i < toIndex; i++) { + mNames[fromIndex] = mNames[i]; + mNames[i] = firstToken; + + insertNameVariants(rawContactId, dataId, fromIndex + 1, toIndex, + initiallyExact && i == fromIndex, buildCollationKey); + + mNames[i] = mNames[fromIndex]; + mNames[fromIndex] = firstToken; + } + } + + /** + * Inserts a single name variant and optionally its collation key counterpart. + */ + private void insertNameVariant(long rawContactId, long dataId, int tokenCount, + int lookupType, boolean buildCollationKey) { + mStringBuilder1.setLength(0); + + for (int i = 0; i < tokenCount; i++) { + if (i != 0) { + mStringBuilder1.append('.'); + } + mStringBuilder1.append(mNames[i]); + } + + insertNameLookup(rawContactId, dataId, lookupType, mStringBuilder1.toString()); + + if (buildCollationKey) { + mStringBuilder2.setLength(0); + + for (int i = 0; i < tokenCount; i++) { + mStringBuilder2.append(mNames[i]); + } + + insertNameLookup(rawContactId, dataId, NameLookupType.NAME_COLLATION_KEY, + mStringBuilder2.toString()); + } + } + + /** + * For all tokens that correspond to nickname clusters, substitutes each cluster key + * and inserts all permutations with that key. + */ + private void insertNicknamePermutations(long rawContactId, long dataId, int fromIndex, + int tokenCount) { + for (int i = fromIndex; i < tokenCount; i++) { + String[] clusters = mNicknameClusters[i]; + if (clusters != null) { + String token = mNames[i]; + for (int j = 0; j < clusters.length; j++) { + mNames[i] = clusters[j]; + + // Insert all permutations with this nickname cluster + insertNameVariants(rawContactId, dataId, 0, tokenCount, false, false); + + // Repeat recursively for other nickname clusters + insertNicknamePermutations(rawContactId, dataId, i + 1, tokenCount); + } + mNames[i] = token; + } + } + } +} diff --git a/src/com/android/providers/contacts/NameNormalizer.java b/src/com/android/providers/contacts/NameNormalizer.java index eca40fc..f40a632 100644 --- a/src/com/android/providers/contacts/NameNormalizer.java +++ b/src/com/android/providers/contacts/NameNormalizer.java @@ -45,7 +45,7 @@ public class NameNormalizer { * of names. It ignores non-letter characters and removes accents. */ public static String normalize(String name) { - return Hex.encodeHex(sCompressingCollator.getSortKey(lettersOnly(name)), true); + return Hex.encodeHex(sCompressingCollator.getSortKey(lettersAndDigitsOnly(name)), true); } /** @@ -53,7 +53,8 @@ public class NameNormalizer { * of mixed case characters, accents and, if all else is equal, length. */ public static int compareComplexity(String name1, String name2) { - int diff = sComplexityCollator.compare(lettersOnly(name1), lettersOnly(name2)); + int diff = sComplexityCollator.compare(lettersAndDigitsOnly(name1), + lettersAndDigitsOnly(name2)); if (diff != 0) { return diff; } @@ -64,12 +65,12 @@ public class NameNormalizer { /** * Returns a string containing just the letters from the original string. */ - private static String lettersOnly(String name) { + private static String lettersAndDigitsOnly(String name) { char[] letters = name.toCharArray(); int length = 0; for (int i = 0; i < letters.length; i++) { final char c = letters[i]; - if (Character.isLetter(c)) { + if (Character.isLetterOrDigit(c)) { letters[length++] = c; } } diff --git a/src/com/android/providers/contacts/NameSplitter.java b/src/com/android/providers/contacts/NameSplitter.java index 258df10..9ceb444 100644 --- a/src/com/android/providers/contacts/NameSplitter.java +++ b/src/com/android/providers/contacts/NameSplitter.java @@ -16,14 +16,13 @@ package com.android.providers.contacts; import android.content.ContentValues; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.text.TextUtils; import java.util.HashSet; import java.util.Locale; import java.util.StringTokenizer; -import android.provider.ContactsContract.CommonDataKinds.StructuredName; -import android.text.TextUtils; - /** * The purpose of this class is to split a full name into given names and last * name. The logic only supports having a single last name. If the full name has @@ -41,6 +40,8 @@ import android.text.TextUtils; */ public class NameSplitter { + public static final int MAX_TOKENS = 10; + private final HashSet<String> mPrefixesSet; private final HashSet<String> mSuffixesSet; private final int mMaxSuffixLength; @@ -105,7 +106,6 @@ public class NameSplitter { } private static class NameTokenizer extends StringTokenizer { - private static final int MAX_TOKENS = 10; private final String[] mTokens; private int mDotBitmask; private int mStartPointer; @@ -191,6 +191,33 @@ public class NameSplitter { } /** + * Parses a full name and returns components as a list of tokens. + */ + public int tokenize(String[] tokens, String fullName) { + if (fullName == null) { + return 0; + } + + NameTokenizer tokenizer = new NameTokenizer(fullName); + + if (tokenizer.mStartPointer == tokenizer.mEndPointer) { + return 0; + } + + String firstToken = tokenizer.mTokens[tokenizer.mStartPointer]; + if (mPrefixesSet.contains(firstToken.toUpperCase())) { + tokenizer.mStartPointer++; + } + int count = 0; + for (int i = tokenizer.mStartPointer; i < tokenizer.mEndPointer; i++) { + tokens[count++] = tokenizer.mTokens[i]; + } + + return count; + } + + + /** * Parses a full name and returns parsed components in the Name object. */ public void split(Name name, String fullName) { diff --git a/src/com/android/providers/contacts/OpenHelper.java b/src/com/android/providers/contacts/OpenHelper.java index e68b736..9ac205f 100644 --- a/src/com/android/providers/contacts/OpenHelper.java +++ b/src/com/android/providers/contacts/OpenHelper.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.database.Cursor; import android.database.DatabaseUtils; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; @@ -46,9 +45,6 @@ import android.provider.ContactsContract.CommonDataKinds.GroupMembership; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.SocialContract.Activities; import android.telephony.PhoneNumberUtils; -import android.text.TextUtils; -import android.text.util.Rfc822Token; -import android.text.util.Rfc822Tokenizer; import android.util.Log; import java.util.HashMap; @@ -62,7 +58,7 @@ import java.util.HashMap; /* package */ class OpenHelper extends SQLiteOpenHelper { private static final String TAG = "OpenHelper"; - private static final int DATABASE_VERSION = 91; + private static final int DATABASE_VERSION = 93; private static final String DATABASE_NAME = "contacts2.db"; private static final String DATABASE_PRESENCE = "presence_db"; @@ -430,12 +426,6 @@ import java.util.HashMap; String CONTACT_ID = "presence_contact_id"; } - private static final String[] NICKNAME_LOOKUP_COLUMNS = new String[] { - NicknameLookupColumns.CLUSTER - }; - - private static final int COL_NICKNAME_LOOKUP_CLUSTER = 0; - /** In-memory cache of previously found mimetype mappings */ private final HashMap<String, Long> mMimetypeCache = new HashMap<String, Long>(); /** In-memory cache of previously found package name mappings */ @@ -452,15 +442,13 @@ import java.util.HashMap; private SQLiteStatement mContactIdAndMarkAggregatedUpdate; private SQLiteStatement mMimetypeInsert; private SQLiteStatement mPackageInsert; - private SQLiteStatement mNameLookupInsert; - private SQLiteStatement mNameLookupDelete; private SQLiteStatement mDataMimetypeQuery; private SQLiteStatement mActivitiesMimetypeQuery; private final Context mContext; private final SyncStateContentProviderHelper mSyncState; - private HashMap<String, String[]> mNicknameClusterCache; + /** Compiled statements for updating {@link Contacts#IN_VISIBLE_GROUP}. */ private SQLiteStatement mVisibleUpdate; @@ -534,12 +522,6 @@ import java.util.HashMap; mActivitiesMimetypeQuery = db.compileStatement("SELECT " + MimetypesColumns.MIMETYPE + " FROM " + Tables.ACTIVITIES_JOIN_MIMETYPES + " WHERE " + Tables.ACTIVITIES + "." + Activities._ID + "=?"); - mNameLookupInsert = db.compileStatement("INSERT OR IGNORE INTO " + Tables.NAME_LOOKUP + "(" - + NameLookupColumns.RAW_CONTACT_ID + "," + NameLookupColumns.DATA_ID + "," - + NameLookupColumns.NAME_TYPE + "," + NameLookupColumns.NORMALIZED_NAME - + ") VALUES (?,?,?,?)"); - mNameLookupDelete = db.compileStatement("DELETE FROM " + Tables.NAME_LOOKUP + " WHERE " - + NameLookupColumns.DATA_ID + "=?"); // Compile statements for updating visibility final String visibleUpdate = "UPDATE " + Tables.CONTACTS + " SET " @@ -702,9 +684,9 @@ import java.util.HashMap; /** * For email lookup and similar queries. */ - db.execSQL("CREATE INDEX data_mimetype_data2_index ON " + Tables.DATA + " (" + + db.execSQL("CREATE INDEX data_mimetype_data1_index ON " + Tables.DATA + " (" + DataColumns.MIMETYPE_ID + "," + - Data.DATA2 + + Data.DATA1 + ");"); /** @@ -964,7 +946,8 @@ import java.util.HashMap; + DataColumns.CONCRETE_MIMETYPE_ID + "=" + MimetypesColumns.CONCRETE_ID + ")" + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE - + "' AND " + GroupsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_DATA1 + ")"); + + "' AND " + GroupsColumns.CONCRETE_ID + "=" + + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")"); String dataColumns = @@ -1036,7 +1019,8 @@ import java.util.HashMap; + DataColumns.CONCRETE_RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")" + " LEFT OUTER JOIN " + Tables.GROUPS + " ON (" + MimetypesColumns.CONCRETE_MIMETYPE + "='" + GroupMembership.CONTENT_ITEM_TYPE - + "' AND " + GroupsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_DATA1 + ")" + + "' AND " + GroupsColumns.CONCRETE_ID + "=" + + Tables.DATA + "." + GroupMembership.GROUP_ROW_ID + ")" + " LEFT OUTER JOIN " + Tables.CONTACTS + " ON (" + RawContacts.CONTACT_ID + "=" + Tables.CONTACTS + "." + Contacts._ID + ")"; @@ -1428,127 +1412,6 @@ import java.util.HashMap; } } - /** - * Deletes all {@link Tables#NAME_LOOKUP} table rows associated with the specified data element. - */ - public void deleteNameLookup(long dataId) { - getWritableDatabase(); - DatabaseUtils.bindObjectToProgram(mNameLookupDelete, 1, dataId); - mNameLookupDelete.execute(); - } - - /** - * Inserts a record in the {@link Tables#NAME_LOOKUP} table. - */ - public void insertNameLookup(long rawContactId, long dataId, int lookupType, String name) { - getWritableDatabase(); - DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 1, rawContactId); - DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 2, dataId); - DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 3, lookupType); - DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 4, name); - mNameLookupInsert.executeInsert(); - } - - - /** - * Inserts name lookup records for the given structured name. - */ - public void insertNameLookupForStructuredName(long rawContactId, long dataId, - String givenName, String familyName) { - if (TextUtils.isEmpty(givenName)) { - - // If neither the first nor last name are specified, nothing to insert - if (TextUtils.isEmpty(familyName)) { - return; - } - - insertNameLookupForSingleName(rawContactId, dataId, familyName); - } else if (TextUtils.isEmpty(familyName)) { - insertNameLookupForSingleName(rawContactId, dataId, givenName); - } else { - insertNameLookupForFullName(rawContactId, dataId, givenName, familyName); - } - } - - private void insertNameLookupForSingleName(long rawContactId, long dataId, String name) { - String nameN = NameNormalizer.normalize(name); - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_EXACT, nameN); - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_COLLATION_KEY, nameN); - - // Take care of first and last names swapped - String[] clusters = getCommonNicknameClusters(nameN); - if (clusters != null) { - for (int i = 0; i < clusters.length; i++) { - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_VARIANT, clusters[i]); - } - } - } - - private void insertNameLookupForFullName(long rawContactId, long dataId, String givenName, - String familyName) { - final String givenNameN = NameNormalizer.normalize(givenName); - final String[] givenNameNicknames = getCommonNicknameClusters(givenNameN); - final String familyNameN = NameNormalizer.normalize(familyName); - final String[] familyNameNicknames = getCommonNicknameClusters(familyNameN); - insertNameLookup(rawContactId, dataId, - NameLookupType.NAME_EXACT, givenNameN + "." + familyNameN); - insertNameLookup(rawContactId, dataId, - NameLookupType.NAME_VARIANT, familyNameN + "." + givenNameN); - insertNameLookup(rawContactId, dataId, - NameLookupType.NAME_COLLATION_KEY, givenNameN + familyNameN); - insertNameLookup(rawContactId, dataId, - NameLookupType.NAME_COLLATION_KEY, familyNameN + givenNameN); - - if (givenNameNicknames != null) { - for (int i = 0; i < givenNameNicknames.length; i++) { - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_VARIANT, - givenNameNicknames[i] + "." + familyNameN); - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_VARIANT, - familyNameN + "." + givenNameNicknames[i]); - } - } - if (familyNameNicknames != null) { - for (int i = 0; i < familyNameNicknames.length; i++) { - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_VARIANT, - familyNameNicknames[i] + "." + givenNameN); - insertNameLookup(rawContactId, dataId, NameLookupType.NAME_VARIANT, - givenNameN + "." + familyNameNicknames[i]); - } - } - } - - public void insertNameLookupForEmail(long rawContactId, long dataId, String email) { - if (TextUtils.isEmpty(email)) { - return; - } - - Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(email); - if (tokens.length == 0) { - return; - } - - String address = tokens[0].getAddress(); - int at = address.indexOf('@'); - if (at != -1) { - address = address.substring(0, at); - } - - insertNameLookup(rawContactId, dataId, - NameLookupType.EMAIL_BASED_NICKNAME, NameNormalizer.normalize(address)); - } - - /** - * Normalizes the nickname and inserts it in the name lookup table. - */ - public void insertNameLookupForNickname(long rawContactId, long dataId, String nickname) { - if (TextUtils.isEmpty(nickname)) { - return; - } - - insertNameLookup(rawContactId, dataId, - NameLookupType.NICKNAME, NameNormalizer.normalize(nickname)); - } - public void buildPhoneLookupAndRawContactQuery(SQLiteQueryBuilder qb, String number) { String normalizedNumber = PhoneNumberUtils.toCallerIDMinMatch(number); qb.setTables(Tables.DATA_JOIN_RAW_CONTACTS + @@ -1639,52 +1502,6 @@ import java.util.HashMap; } } - /** - * Returns common nickname cluster IDs for a given name. For example, it - * will return the same value for "Robert", "Bob" and "Rob". Some names belong to multiple - * clusters, e.g. Leo could be Leonard or Leopold. - * - * May return null. - * - * @param normalizedName A normalized first name, see {@link NameNormalizer#normalize}. - */ - public String[] getCommonNicknameClusters(String normalizedName) { - if (mNicknameClusterCache == null) { - mNicknameClusterCache = new HashMap<String, String[]>(); - } - - synchronized (mNicknameClusterCache) { - if (mNicknameClusterCache.containsKey(normalizedName)) { - return mNicknameClusterCache.get(normalizedName); - } - } - - String[] clusters = null; - SQLiteDatabase db = getReadableDatabase(); - - Cursor cursor = db.query(Tables.NICKNAME_LOOKUP, NICKNAME_LOOKUP_COLUMNS, - NicknameLookupColumns.NAME + "=?", new String[] { normalizedName }, - null, null, null); - try { - int count = cursor.getCount(); - if (count > 0) { - clusters = new String[count]; - for (int i = 0; i < count; i++) { - cursor.moveToNext(); - clusters[i] = cursor.getString(COL_NICKNAME_LOOKUP_CLUSTER); - } - } - } finally { - cursor.close(); - } - - synchronized (mNicknameClusterCache) { - mNicknameClusterCache.put(normalizedName, clusters); - } - - return clusters; - } - public static void copyStringValue(ContentValues toValues, String toKey, ContentValues fromValues, String fromKey) { if (fromValues.containsKey(fromKey)) { |