From f23764675b35b5262a39c79aad8e9842460274b2 Mon Sep 17 00:00:00 2001 From: Dmitri Plotnikov Date: Sun, 20 Sep 2009 16:56:40 -0700 Subject: 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 --- .../providers/contacts/ContactAggregator.java | 199 +-- .../providers/contacts/ContactsProvider2.java | 199 ++- .../providers/contacts/GlobalSearchSupport.java | 16 +- .../providers/contacts/LegacyApiSupport.java | 6 +- .../providers/contacts/LegacyContactImporter.java | 11 +- .../providers/contacts/NameLookupBuilder.java | 166 ++ .../android/providers/contacts/NameNormalizer.java | 9 +- .../android/providers/contacts/NameSplitter.java | 35 +- src/com/android/providers/contacts/OpenHelper.java | 199 +-- tests/assets/expected_data.txt | 1687 ++++++++++++-------- .../providers/contacts/ContactAggregatorTest.java | 51 + .../contacts/LegacyContactImporterTest.java | 10 + .../providers/contacts/NameLookupBuilderTest.java | 174 ++ 13 files changed, 1756 insertions(+), 1006 deletions(-) create mode 100644 src/com/android/providers/contacts/NameLookupBuilder.java create mode 100644 tests/src/com/android/providers/contacts/NameLookupBuilderTest.java 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 sDisplayNameSources; static { sDisplayNameSources = new HashMap(); @@ -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> mNicknameClusterCache = + new HashMap>(); 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(); 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 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(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 mPrefixesSet; private final HashSet 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 mMimetypeCache = new HashMap(); /** 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 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(); - } - - 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)) { diff --git a/tests/assets/expected_data.txt b/tests/assets/expected_data.txt index 87fac2a..a42526d 100644 --- a/tests/assets/expected_data.txt +++ b/tests/assets/expected_data.txt @@ -2,454 +2,454 @@ 2 _id=1 3 raw_contact_id=1 4 mimetype=vnd.android.cursor.item/name -5 data1=Test -6 data2=Android -7 data3=null +5 data1=Test Android +6 data2=Test +7 data3=Android 8 data4=null -9 data9=Test Android -10 is_primary=0 -11 is_super_primary=0 -12 data_version=0 -13 data_sync1=null -14 data_sync2=null -15 data_sync3=null -16 data_sync4=null -17 } -18 1 { -19 _id=2 -20 raw_contact_id=2 -21 mimetype=vnd.android.cursor.item/name -22 data1=Jane -23 data2=Doe -24 data3=null -25 data4=null -26 data9=Jane Doe -27 is_primary=0 -28 is_super_primary=0 -29 data_version=0 -30 data_sync1=null -31 data_sync2=null -32 data_sync3=null -33 data_sync4=null -34 } -35 2 { -36 _id=3 -37 raw_contact_id=3 -38 mimetype=vnd.android.cursor.item/name -39 data1=John -40 data2=Doe -41 data3=null -42 data4=null -43 data9=John Doe -44 is_primary=0 -45 is_super_primary=0 -46 data_version=0 -47 data_sync1=null -48 data_sync2=null -49 data_sync3=null -50 data_sync4=null -51 } -52 3 { -53 _id=4 -54 raw_contact_id=3 -55 mimetype=vnd.android.cursor.item/note -56 data1=This is a test account for Eclair Android Contacts -57 data2=null -58 data3=null -59 data4=null -60 data9=null -61 is_primary=0 -62 is_super_primary=0 -63 data_version=0 -64 data_sync1=null -65 data_sync2=null -66 data_sync3=null -67 data_sync4=null -68 } -69 4 { -70 _id=5 -71 raw_contact_id=11 -72 mimetype=vnd.android.cursor.item/name -73 data1=Added -74 data2=Android -75 data3=null -76 data4=On -77 data9=Added On Android -78 is_primary=0 -79 is_super_primary=0 -80 data_version=0 -81 data_sync1=null -82 data_sync2=null -83 data_sync3=null -84 data_sync4=null -85 } -86 5 { -87 _id=6 -88 raw_contact_id=3 -89 mimetype=vnd.android.cursor.item/organization -90 data1=1 -91 data2=Acme Corp -92 data3=null -93 data4=President +9 data5=null +10 data6=null +11 data7=null +12 data8=null +13 data9=null +14 data10=null +15 data11=null +16 data12=null +17 data13=null +18 data14=null +19 data15=null +20 is_primary=0 +21 is_super_primary=0 +22 data_version=0 +23 data_sync1=null +24 data_sync2=null +25 data_sync3=null +26 data_sync4=null +27 } +28 1 { +29 _id=2 +30 raw_contact_id=2 +31 mimetype=vnd.android.cursor.item/name +32 data1=Jane Doe +33 data2=Jane +34 data3=Doe +35 data4=null +36 data5=null +37 data6=null +38 data7=null +39 data8=null +40 data9=null +41 data10=null +42 data11=null +43 data12=null +44 data13=null +45 data14=null +46 data15=null +47 is_primary=0 +48 is_super_primary=0 +49 data_version=0 +50 data_sync1=null +51 data_sync2=null +52 data_sync3=null +53 data_sync4=null +54 } +55 2 { +56 _id=3 +57 raw_contact_id=3 +58 mimetype=vnd.android.cursor.item/name +59 data1=John Doe +60 data2=John +61 data3=Doe +62 data4=null +63 data5=null +64 data6=null +65 data7=null +66 data8=null +67 data9=null +68 data10=null +69 data11=null +70 data12=null +71 data13=null +72 data14=null +73 data15=null +74 is_primary=0 +75 is_super_primary=0 +76 data_version=0 +77 data_sync1=null +78 data_sync2=null +79 data_sync3=null +80 data_sync4=null +81 } +82 3 { +83 _id=4 +84 raw_contact_id=3 +85 mimetype=vnd.android.cursor.item/note +86 data1=This is a test account for Eclair Android Contacts +87 data2=null +88 data3=null +89 data4=null +90 data5=null +91 data6=null +92 data7=null +93 data8=null 94 data9=null -95 is_primary=0 -96 is_super_primary=0 -97 data_version=0 -98 data_sync1=null -99 data_sync2=null -100 data_sync3=null -101 data_sync4=null -102 } -103 6 { -104 _id=7 -105 raw_contact_id=2 -106 mimetype=vnd.android.cursor.item/phone_v2 -107 data1=1 -108 data2=1-800-466-4411 -109 data3=null -110 data4=11446640081 -111 data9=null -112 is_primary=1 -113 is_super_primary=0 -114 data_version=0 -115 data_sync1=null -116 data_sync2=null -117 data_sync3=null -118 data_sync4=null -119 } -120 7 { -121 _id=8 -122 raw_contact_id=2 -123 mimetype=vnd.android.cursor.item/phone_v2 -124 data1=3 -125 data2=2345678901 -126 data3=null -127 data4=1098765432 -128 data9=null -129 is_primary=0 -130 is_super_primary=0 -131 data_version=0 -132 data_sync1=null -133 data_sync2=null -134 data_sync3=null -135 data_sync4=null -136 } -137 8 { -138 _id=9 -139 raw_contact_id=2 -140 mimetype=vnd.android.cursor.item/phone_v2 -141 data1=2 -142 data2=3456789012 -143 data3=null -144 data4=2109876543 -145 data9=null -146 is_primary=0 -147 is_super_primary=0 -148 data_version=0 -149 data_sync1=null -150 data_sync2=null -151 data_sync3=null -152 data_sync4=null -153 } -154 9 { -155 _id=10 -156 raw_contact_id=2 -157 mimetype=vnd.android.cursor.item/phone_v2 -158 data1=5 -159 data2=4567890123 -160 data3=null -161 data4=3210987654 -162 data9=null -163 is_primary=0 -164 is_super_primary=0 -165 data_version=0 -166 data_sync1=null -167 data_sync2=null -168 data_sync3=null -169 data_sync4=null -170 } -171 10 { -172 _id=11 -173 raw_contact_id=2 -174 mimetype=vnd.android.cursor.item/phone_v2 -175 data1=4 -176 data2=5678901234 -177 data3=null -178 data4=4321098765 -179 data9=null -180 is_primary=0 -181 is_super_primary=0 -182 data_version=0 -183 data_sync1=null -184 data_sync2=null -185 data_sync3=null -186 data_sync4=null -187 } -188 11 { -189 _id=12 -190 raw_contact_id=2 -191 mimetype=vnd.android.cursor.item/phone_v2 -192 data1=6 -193 data2=6789012345 -194 data3=null -195 data4=5432109876 -196 data9=null -197 is_primary=0 -198 is_super_primary=0 -199 data_version=0 -200 data_sync1=null -201 data_sync2=null -202 data_sync3=null -203 data_sync4=null -204 } -205 12 { -206 _id=13 -207 raw_contact_id=2 -208 mimetype=vnd.android.cursor.item/phone_v2 -209 data1=7 -210 data2=7890123456 -211 data3=null -212 data4=6543210987 -213 data9=null -214 is_primary=0 -215 is_super_primary=0 -216 data_version=0 -217 data_sync1=null -218 data_sync2=null -219 data_sync3=null -220 data_sync4=null -221 } -222 13 { -223 _id=14 -224 raw_contact_id=3 -225 mimetype=vnd.android.cursor.item/phone_v2 -226 data1=2 -227 data2=555-555-5555 -228 data3=null -229 data4=5555555555 -230 data9=null -231 is_primary=0 -232 is_super_primary=0 -233 data_version=0 -234 data_sync1=null -235 data_sync2=null -236 data_sync3=null -237 data_sync4=null -238 } -239 14 { -240 _id=15 -241 raw_contact_id=2 -242 mimetype=vnd.android.cursor.item/phone_v2 -243 data1=1 -244 data2=1234567890 -245 data3=null -246 data4=0987654321 -247 data9=null -248 is_primary=0 -249 is_super_primary=0 -250 data_version=0 -251 data_sync1=null -252 data_sync2=null -253 data_sync3=null -254 data_sync4=null -255 } -256 15 { -257 _id=16 -258 raw_contact_id=11 -259 mimetype=vnd.android.cursor.item/phone_v2 -260 data1=2 -261 data2=1-987-4563 -262 data3=null -263 data4=36547891 -264 data9=null -265 is_primary=1 -266 is_super_primary=0 -267 data_version=0 -268 data_sync1=null -269 data_sync2=null -270 data_sync3=null -271 data_sync4=null -272 } -273 16 { -274 _id=17 -275 raw_contact_id=2 -276 mimetype=vnd.android.cursor.item/email_v2 -277 data1=1 -278 data2=a@acme.com -279 data3=null -280 data4=null -281 data9=null -282 is_primary=1 -283 is_super_primary=0 -284 data_version=0 -285 data_sync1=null -286 data_sync2=null -287 data_sync3=null -288 data_sync4=null -289 } -290 17 { -291 _id=18 -292 raw_contact_id=2 -293 mimetype=vnd.android.cursor.item/email_v2 -294 data1=2 -295 data2=b@acme.com -296 data3=null -297 data4=null -298 data9=null -299 is_primary=0 -300 is_super_primary=0 -301 data_version=0 -302 data_sync1=null -303 data_sync2=null -304 data_sync3=null -305 data_sync4=null -306 } -307 18 { -308 _id=19 -309 raw_contact_id=2 -310 mimetype=vnd.android.cursor.item/email_v2 -311 data1=3 -312 data2=c@acme.com -313 data3=null -314 data4=null -315 data9=null -316 is_primary=0 -317 is_super_primary=0 -318 data_version=0 -319 data_sync1=null -320 data_sync2=null -321 data_sync3=null -322 data_sync4=null -323 } -324 19 { -325 _id=20 -326 raw_contact_id=2 -327 mimetype=vnd.android.cursor.item/email_v2 -328 data1=3 -329 data2=d@acme.com -330 data3=null -331 data4=null -332 data9=null -333 is_primary=0 -334 is_super_primary=0 -335 data_version=0 -336 data_sync1=null -337 data_sync2=null -338 data_sync3=null -339 data_sync4=null -340 } -341 20 { -342 _id=21 -343 raw_contact_id=2 -344 mimetype=vnd.android.cursor.item/im -345 data1=3 -346 data2=a -347 data3=null -348 data4=null -349 data9=null -350 is_primary=0 -351 is_super_primary=0 -352 data_version=0 -353 data_sync1=null -354 data_sync2=null -355 data_sync3=null -356 data_sync4=null -357 } -358 21 { -359 _id=22 -360 raw_contact_id=2 -361 mimetype=vnd.android.cursor.item/im -362 data1=3 -363 data2=b -364 data3=null -365 data4=null -366 data9=null -367 is_primary=0 -368 is_super_primary=0 -369 data_version=0 -370 data_sync1=null -371 data_sync2=null -372 data_sync3=null -373 data_sync4=null -374 } -375 22 { -376 _id=23 -377 raw_contact_id=2 -378 mimetype=vnd.android.cursor.item/im -379 data1=3 -380 data2=c -381 data3=null -382 data4=null -383 data9=null -384 is_primary=0 -385 is_super_primary=0 -386 data_version=0 -387 data_sync1=null -388 data_sync2=null -389 data_sync3=null -390 data_sync4=null -391 } -392 23 { -393 _id=24 -394 raw_contact_id=2 -395 mimetype=vnd.android.cursor.item/im -396 data1=3 -397 data2=d -398 data3=null -399 data4=null -400 data9=null -401 is_primary=0 -402 is_super_primary=0 -403 data_version=0 -404 data_sync1=null -405 data_sync2=null -406 data_sync3=null -407 data_sync4=null -408 } -409 24 { -410 _id=25 -411 raw_contact_id=2 -412 mimetype=vnd.android.cursor.item/im -413 data1=3 -414 data2=e -415 data3=null -416 data4=null -417 data9=null -418 is_primary=0 -419 is_super_primary=0 -420 data_version=0 -421 data_sync1=null -422 data_sync2=null -423 data_sync3=null -424 data_sync4=null -425 } -426 25 { -427 _id=26 -428 raw_contact_id=2 -429 mimetype=vnd.android.cursor.item/im -430 data1=3 -431 data2=f -432 data3=null -433 data4=null -434 data9=null -435 is_primary=0 -436 is_super_primary=0 -437 data_version=0 -438 data_sync1=null -439 data_sync2=null -440 data_sync3=null -441 data_sync4=null -442 } -443 26 { -444 _id=27 -445 raw_contact_id=2 -446 mimetype=vnd.android.cursor.item/im -447 data1=3 -448 data2=g -449 data3=null -450 data4=null -451 data9=null -452 is_primary=0 +95 data10=null +96 data11=null +97 data12=null +98 data13=null +99 data14=null +100 data15=null +101 is_primary=0 +102 is_super_primary=0 +103 data_version=0 +104 data_sync1=null +105 data_sync2=null +106 data_sync3=null +107 data_sync4=null +108 } +109 4 { +110 _id=5 +111 raw_contact_id=11 +112 mimetype=vnd.android.cursor.item/name +113 data1=Added On Android +114 data2=Added +115 data3=Android +116 data4=null +117 data5=On +118 data6=null +119 data7=null +120 data8=null +121 data9=null +122 data10=null +123 data11=null +124 data12=null +125 data13=null +126 data14=null +127 data15=null +128 is_primary=0 +129 is_super_primary=0 +130 data_version=0 +131 data_sync1=null +132 data_sync2=null +133 data_sync3=null +134 data_sync4=null +135 } +136 5 { +137 _id=6 +138 raw_contact_id=3 +139 mimetype=vnd.android.cursor.item/organization +140 data1=Acme Corp +141 data2=1 +142 data3=null +143 data4=President +144 data5=null +145 data6=null +146 data7=null +147 data8=null +148 data9=null +149 data10=null +150 data11=null +151 data12=null +152 data13=null +153 data14=null +154 data15=null +155 is_primary=0 +156 is_super_primary=0 +157 data_version=0 +158 data_sync1=null +159 data_sync2=null +160 data_sync3=null +161 data_sync4=null +162 } +163 6 { +164 _id=7 +165 raw_contact_id=2 +166 mimetype=vnd.android.cursor.item/phone_v2 +167 data1=1-800-466-4411 +168 data2=1 +169 data3=null +170 data4=11446640081 +171 data5=null +172 data6=null +173 data7=null +174 data8=null +175 data9=null +176 data10=null +177 data11=null +178 data12=null +179 data13=null +180 data14=null +181 data15=null +182 is_primary=1 +183 is_super_primary=0 +184 data_version=0 +185 data_sync1=null +186 data_sync2=null +187 data_sync3=null +188 data_sync4=null +189 } +190 7 { +191 _id=8 +192 raw_contact_id=2 +193 mimetype=vnd.android.cursor.item/phone_v2 +194 data1=2345678901 +195 data2=3 +196 data3=null +197 data4=1098765432 +198 data5=null +199 data6=null +200 data7=null +201 data8=null +202 data9=null +203 data10=null +204 data11=null +205 data12=null +206 data13=null +207 data14=null +208 data15=null +209 is_primary=0 +210 is_super_primary=0 +211 data_version=0 +212 data_sync1=null +213 data_sync2=null +214 data_sync3=null +215 data_sync4=null +216 } +217 8 { +218 _id=9 +219 raw_contact_id=2 +220 mimetype=vnd.android.cursor.item/phone_v2 +221 data1=3456789012 +222 data2=2 +223 data3=null +224 data4=2109876543 +225 data5=null +226 data6=null +227 data7=null +228 data8=null +229 data9=null +230 data10=null +231 data11=null +232 data12=null +233 data13=null +234 data14=null +235 data15=null +236 is_primary=0 +237 is_super_primary=0 +238 data_version=0 +239 data_sync1=null +240 data_sync2=null +241 data_sync3=null +242 data_sync4=null +243 } +244 9 { +245 _id=10 +246 raw_contact_id=2 +247 mimetype=vnd.android.cursor.item/phone_v2 +248 data1=4567890123 +249 data2=5 +250 data3=null +251 data4=3210987654 +252 data5=null +253 data6=null +254 data7=null +255 data8=null +256 data9=null +257 data10=null +258 data11=null +259 data12=null +260 data13=null +261 data14=null +262 data15=null +263 is_primary=0 +264 is_super_primary=0 +265 data_version=0 +266 data_sync1=null +267 data_sync2=null +268 data_sync3=null +269 data_sync4=null +270 } +271 10 { +272 _id=11 +273 raw_contact_id=2 +274 mimetype=vnd.android.cursor.item/phone_v2 +275 data1=5678901234 +276 data2=4 +277 data3=null +278 data4=4321098765 +279 data5=null +280 data6=null +281 data7=null +282 data8=null +283 data9=null +284 data10=null +285 data11=null +286 data12=null +287 data13=null +288 data14=null +289 data15=null +290 is_primary=0 +291 is_super_primary=0 +292 data_version=0 +293 data_sync1=null +294 data_sync2=null +295 data_sync3=null +296 data_sync4=null +297 } +298 11 { +299 _id=12 +300 raw_contact_id=2 +301 mimetype=vnd.android.cursor.item/phone_v2 +302 data1=6789012345 +303 data2=6 +304 data3=null +305 data4=5432109876 +306 data5=null +307 data6=null +308 data7=null +309 data8=null +310 data9=null +311 data10=null +312 data11=null +313 data12=null +314 data13=null +315 data14=null +316 data15=null +317 is_primary=0 +318 is_super_primary=0 +319 data_version=0 +320 data_sync1=null +321 data_sync2=null +322 data_sync3=null +323 data_sync4=null +324 } +325 12 { +326 _id=13 +327 raw_contact_id=2 +328 mimetype=vnd.android.cursor.item/phone_v2 +329 data1=7890123456 +330 data2=7 +331 data3=null +332 data4=6543210987 +333 data5=null +334 data6=null +335 data7=null +336 data8=null +337 data9=null +338 data10=null +339 data11=null +340 data12=null +341 data13=null +342 data14=null +343 data15=null +344 is_primary=0 +345 is_super_primary=0 +346 data_version=0 +347 data_sync1=null +348 data_sync2=null +349 data_sync3=null +350 data_sync4=null +351 } +352 13 { +353 _id=14 +354 raw_contact_id=3 +355 mimetype=vnd.android.cursor.item/phone_v2 +356 data1=555-555-5555 +357 data2=2 +358 data3=null +359 data4=5555555555 +360 data5=null +361 data6=null +362 data7=null +363 data8=null +364 data9=null +365 data10=null +366 data11=null +367 data12=null +368 data13=null +369 data14=null +370 data15=null +371 is_primary=0 +372 is_super_primary=0 +373 data_version=0 +374 data_sync1=null +375 data_sync2=null +376 data_sync3=null +377 data_sync4=null +378 } +379 14 { +380 _id=15 +381 raw_contact_id=2 +382 mimetype=vnd.android.cursor.item/phone_v2 +383 data1=1234567890 +384 data2=1 +385 data3=null +386 data4=0987654321 +387 data5=null +388 data6=null +389 data7=null +390 data8=null +391 data9=null +392 data10=null +393 data11=null +394 data12=null +395 data13=null +396 data14=null +397 data15=null +398 is_primary=0 +399 is_super_primary=0 +400 data_version=0 +401 data_sync1=null +402 data_sync2=null +403 data_sync3=null +404 data_sync4=null +405 } +406 15 { +407 _id=16 +408 raw_contact_id=11 +409 mimetype=vnd.android.cursor.item/phone_v2 +410 data1=1-987-4563 +411 data2=2 +412 data3=null +413 data4=36547891 +414 data5=null +415 data6=null +416 data7=null +417 data8=null +418 data9=null +419 data10=null +420 data11=null +421 data12=null +422 data13=null +423 data14=null +424 data15=null +425 is_primary=1 +426 is_super_primary=0 +427 data_version=0 +428 data_sync1=null +429 data_sync2=null +430 data_sync3=null +431 data_sync4=null +432 } +433 16 { +434 _id=17 +435 raw_contact_id=2 +436 mimetype=vnd.android.cursor.item/email_v2 +437 data1=a@acme.com +438 data2=1 +439 data3=null +440 data4=null +441 data5=null +442 data6=null +443 data7=null +444 data8=null +445 data9=null +446 data10=null +447 data11=null +448 data12=null +449 data13=null +450 data14=null +451 data15=null +452 is_primary=1 453 is_super_primary=0 454 data_version=0 455 data_sync1=null @@ -457,209 +457,598 @@ 457 data_sync3=null 458 data_sync4=null 459 } -460 27 { -461 _id=28 +460 17 { +461 _id=18 462 raw_contact_id=2 -463 mimetype=vnd.android.cursor.item/im -464 data1=3 -465 data2=h +463 mimetype=vnd.android.cursor.item/email_v2 +464 data1=b@acme.com +465 data2=2 466 data3=null 467 data4=null -468 data9=null -469 is_primary=0 -470 is_super_primary=0 -471 data_version=0 -472 data_sync1=null -473 data_sync2=null -474 data_sync3=null -475 data_sync4=null -476 } -477 28 { -478 _id=29 -479 raw_contact_id=3 -480 mimetype=vnd.android.cursor.item/email_v2 -481 data1=2 -482 data2=deer@acme.com -483 data3=null -484 data4=null -485 data9=null -486 is_primary=1 -487 is_super_primary=0 -488 data_version=0 -489 data_sync1=null -490 data_sync2=null -491 data_sync3=null -492 data_sync4=null -493 } -494 29 { -495 _id=30 -496 raw_contact_id=3 -497 mimetype=vnd.android.cursor.item/postal-address_v2 -498 data1=2 -499 data2=12345 Main Street -500 Main Town, CA 95000 -501 data3=null -502 data4=null -503 data9=null -504 is_primary=0 -505 is_super_primary=0 -506 data_version=0 -507 data_sync1=null -508 data_sync2=null -509 data_sync3=null -510 data_sync4=null -511 } -512 30 { -513 _id=31 -514 raw_contact_id=3 -515 mimetype=vnd.android.cursor.item/im -516 data1=3 -517 data2=deerdough -518 data3=null -519 data4=null -520 data9=null -521 is_primary=0 -522 is_super_primary=0 -523 data_version=0 -524 data_sync1=null -525 data_sync2=null -526 data_sync3=null -527 data_sync4=null -528 } -529 31 { -530 _id=32 -531 raw_contact_id=2 -532 mimetype=vnd.android.cursor.item/photo -533 data1= -534 data2=null -535 data3=null -536 data4=null -537 data9=null -538 is_primary=0 -539 is_super_primary=0 -540 data_version=0 -541 data_sync1=3d09f37e0f0dbc6f -542 data_sync2=null -543 data_sync3=null -544 data_sync4=null -545 } -546 32 { -547 _id=33 -548 raw_contact_id=3 -549 mimetype=vnd.android.cursor.item/photo -550 data1= -551 data2=null -552 data3=null -553 data4=null -554 data9=null -555 is_primary=0 -556 is_super_primary=0 -557 data_version=0 -558 data_sync1=5c9ae978b346ac9 -559 data_sync2=null -560 data_sync3=null -561 data_sync4=null -562 } -563 33 { -564 _id=34 -565 raw_contact_id=2 -566 mimetype=vnd.android.cursor.item/group_membership -567 data1=1 -568 data2=null -569 data3=null -570 data4=null -571 data9=null -572 is_primary=0 -573 is_super_primary=0 -574 data_version=0 -575 data_sync1=null -576 data_sync2=null -577 data_sync3=null -578 data_sync4=null -579 } -580 34 { -581 _id=35 -582 raw_contact_id=3 -583 mimetype=vnd.android.cursor.item/group_membership -584 data1=1 -585 data2=null -586 data3=null -587 data4=null -588 data9=null -589 is_primary=0 -590 is_super_primary=0 -591 data_version=0 -592 data_sync1=null -593 data_sync2=null -594 data_sync3=null -595 data_sync4=null -596 } -597 35 { -598 _id=36 -599 raw_contact_id=3 -600 mimetype=vnd.android.cursor.item/group_membership -601 data1=2 -602 data2=null -603 data3=null -604 data4=null -605 data9=null -606 is_primary=0 -607 is_super_primary=0 -608 data_version=0 -609 data_sync1=null -610 data_sync2=null -611 data_sync3=null -612 data_sync4=null -613 } -614 36 { -615 _id=37 -616 raw_contact_id=3 -617 mimetype=vnd.android.cursor.item/group_membership -618 data1=4 -619 data2=null -620 data3=null -621 data4=null -622 data9=null -623 is_primary=0 -624 is_super_primary=0 -625 data_version=0 -626 data_sync1=null -627 data_sync2=null -628 data_sync3=null -629 data_sync4=null -630 } -631 37 { -632 _id=38 -633 raw_contact_id=11 -634 mimetype=vnd.android.cursor.item/group_membership -635 data1=1 -636 data2=null -637 data3=null -638 data4=null -639 data9=null -640 is_primary=0 -641 is_super_primary=0 -642 data_version=0 -643 data_sync1=null -644 data_sync2=null -645 data_sync3=null -646 data_sync4=null -647 } -648 38 { -649 _id=39 -650 raw_contact_id=2 -651 mimetype=vnd.android.cursor.item/group_membership -652 data1=3 -653 data2=null -654 data3=null -655 data4=null -656 data9=null -657 is_primary=0 -658 is_super_primary=0 -659 data_version=0 -660 data_sync1=null -661 data_sync2=null -662 data_sync3=null -663 data_sync4=null -664 } - +468 data5=null +469 data6=null +470 data7=null +471 data8=null +472 data9=null +473 data10=null +474 data11=null +475 data12=null +476 data13=null +477 data14=null +478 data15=null +479 is_primary=0 +480 is_super_primary=0 +481 data_version=0 +482 data_sync1=null +483 data_sync2=null +484 data_sync3=null +485 data_sync4=null +486 } +487 18 { +488 _id=19 +489 raw_contact_id=2 +490 mimetype=vnd.android.cursor.item/email_v2 +491 data1=c@acme.com +492 data2=3 +493 data3=null +494 data4=null +495 data5=null +496 data6=null +497 data7=null +498 data8=null +499 data9=null +500 data10=null +501 data11=null +502 data12=null +503 data13=null +504 data14=null +505 data15=null +506 is_primary=0 +507 is_super_primary=0 +508 data_version=0 +509 data_sync1=null +510 data_sync2=null +511 data_sync3=null +512 data_sync4=null +513 } +514 19 { +515 _id=20 +516 raw_contact_id=2 +517 mimetype=vnd.android.cursor.item/email_v2 +518 data1=d@acme.com +519 data2=3 +520 data3=null +521 data4=null +522 data5=null +523 data6=null +524 data7=null +525 data8=null +526 data9=null +527 data10=null +528 data11=null +529 data12=null +530 data13=null +531 data14=null +532 data15=null +533 is_primary=0 +534 is_super_primary=0 +535 data_version=0 +536 data_sync1=null +537 data_sync2=null +538 data_sync3=null +539 data_sync4=null +540 } +541 20 { +542 _id=21 +543 raw_contact_id=2 +544 mimetype=vnd.android.cursor.item/im +545 data1=a +546 data2=3 +547 data3=null +548 data4=null +549 data5=null +550 data6=null +551 data7=null +552 data8=null +553 data9=null +554 data10=null +555 data11=null +556 data12=null +557 data13=null +558 data14=pre:5 +559 data15=null +560 is_primary=0 +561 is_super_primary=0 +562 data_version=0 +563 data_sync1=null +564 data_sync2=null +565 data_sync3=null +566 data_sync4=null +567 } +568 21 { +569 _id=22 +570 raw_contact_id=2 +571 mimetype=vnd.android.cursor.item/im +572 data1=b +573 data2=3 +574 data3=null +575 data4=null +576 data5=null +577 data6=null +578 data7=null +579 data8=null +580 data9=null +581 data10=null +582 data11=null +583 data12=null +584 data13=null +585 data14=pre:0 +586 data15=null +587 is_primary=0 +588 is_super_primary=0 +589 data_version=0 +590 data_sync1=null +591 data_sync2=null +592 data_sync3=null +593 data_sync4=null +594 } +595 22 { +596 _id=23 +597 raw_contact_id=2 +598 mimetype=vnd.android.cursor.item/im +599 data1=c +600 data2=3 +601 data3=null +602 data4=null +603 data5=null +604 data6=null +605 data7=null +606 data8=null +607 data9=null +608 data10=null +609 data11=null +610 data12=null +611 data13=null +612 data14=pre:2 +613 data15=null +614 is_primary=0 +615 is_super_primary=0 +616 data_version=0 +617 data_sync1=null +618 data_sync2=null +619 data_sync3=null +620 data_sync4=null +621 } +622 23 { +623 _id=24 +624 raw_contact_id=2 +625 mimetype=vnd.android.cursor.item/im +626 data1=d +627 data2=3 +628 data3=null +629 data4=null +630 data5=null +631 data6=null +632 data7=null +633 data8=null +634 data9=null +635 data10=null +636 data11=null +637 data12=null +638 data13=null +639 data14=pre:3 +640 data15=null +641 is_primary=0 +642 is_super_primary=0 +643 data_version=0 +644 data_sync1=null +645 data_sync2=null +646 data_sync3=null +647 data_sync4=null +648 } +649 24 { +650 _id=25 +651 raw_contact_id=2 +652 mimetype=vnd.android.cursor.item/im +653 data1=e +654 data2=3 +655 data3=null +656 data4=null +657 data5=null +658 data6=null +659 data7=null +660 data8=null +661 data9=null +662 data10=null +663 data11=null +664 data12=null +665 data13=null +666 data14=pre:4 +667 data15=null +668 is_primary=0 +669 is_super_primary=0 +670 data_version=0 +671 data_sync1=null +672 data_sync2=null +673 data_sync3=null +674 data_sync4=null +675 } +676 25 { +677 _id=26 +678 raw_contact_id=2 +679 mimetype=vnd.android.cursor.item/im +680 data1=f +681 data2=3 +682 data3=null +683 data4=null +684 data5=null +685 data6=null +686 data7=null +687 data8=null +688 data9=null +689 data10=null +690 data11=null +691 data12=null +692 data13=null +693 data14=pre:1 +694 data15=null +695 is_primary=0 +696 is_super_primary=0 +697 data_version=0 +698 data_sync1=null +699 data_sync2=null +700 data_sync3=null +701 data_sync4=null +702 } +703 26 { +704 _id=27 +705 raw_contact_id=2 +706 mimetype=vnd.android.cursor.item/im +707 data1=g +708 data2=3 +709 data3=null +710 data4=null +711 data5=null +712 data6=null +713 data7=null +714 data8=null +715 data9=null +716 data10=null +717 data11=null +718 data12=null +719 data13=null +720 data14=pre:6 +721 data15=null +722 is_primary=0 +723 is_super_primary=0 +724 data_version=0 +725 data_sync1=null +726 data_sync2=null +727 data_sync3=null +728 data_sync4=null +729 } +730 27 { +731 _id=28 +732 raw_contact_id=2 +733 mimetype=vnd.android.cursor.item/im +734 data1=h +735 data2=3 +736 data3=null +737 data4=null +738 data5=null +739 data6=null +740 data7=null +741 data8=null +742 data9=null +743 data10=null +744 data11=null +745 data12=null +746 data13=null +747 data14=pre:7 +748 data15=null +749 is_primary=0 +750 is_super_primary=0 +751 data_version=0 +752 data_sync1=null +753 data_sync2=null +754 data_sync3=null +755 data_sync4=null +756 } +757 28 { +758 _id=29 +759 raw_contact_id=3 +760 mimetype=vnd.android.cursor.item/email_v2 +761 data1=deer@acme.com +762 data2=2 +763 data3=null +764 data4=null +765 data5=null +766 data6=null +767 data7=null +768 data8=null +769 data9=null +770 data10=null +771 data11=null +772 data12=null +773 data13=null +774 data14=null +775 data15=null +776 is_primary=1 +777 is_super_primary=0 +778 data_version=0 +779 data_sync1=null +780 data_sync2=null +781 data_sync3=null +782 data_sync4=null +783 } +784 29 { +785 _id=30 +786 raw_contact_id=3 +787 mimetype=vnd.android.cursor.item/postal-address_v2 +788 data1=12345 Main Street +789 Main Town, CA 95000 +790 data2=2 +791 data3=null +792 data4=null +793 data5=null +794 data6=null +795 data7=null +796 data8=null +797 data9=null +798 data10=null +799 data11=null +800 data12=null +801 data13=null +802 data14=null +803 data15=null +804 is_primary=0 +805 is_super_primary=0 +806 data_version=0 +807 data_sync1=null +808 data_sync2=null +809 data_sync3=null +810 data_sync4=null +811 } +812 30 { +813 _id=31 +814 raw_contact_id=3 +815 mimetype=vnd.android.cursor.item/im +816 data1=deerdough +817 data2=3 +818 data3=null +819 data4=null +820 data5=null +821 data6=null +822 data7=null +823 data8=null +824 data9=null +825 data10=null +826 data11=null +827 data12=null +828 data13=null +829 data14=pre:5 +830 data15=null +831 is_primary=0 +832 is_super_primary=0 +833 data_version=0 +834 data_sync1=null +835 data_sync2=null +836 data_sync3=null +837 data_sync4=null +838 } +839 31 { +840 _id=32 +841 raw_contact_id=2 +842 mimetype=vnd.android.cursor.item/photo +843 data1=null +844 data2=null +845 data3=null +846 data4=null +847 data5=null +848 data6=null +849 data7=null +850 data8=null +851 data9=null +852 data10=null +853 data11=null +854 data12=null +855 data13=null +856 data14=null +857 data15= +858 is_primary=0 +859 is_super_primary=0 +860 data_version=0 +861 data_sync1=3d09f37e0f0dbc6f +862 data_sync2=null +863 data_sync3=null +864 data_sync4=null +865 } +866 32 { +867 _id=33 +868 raw_contact_id=3 +869 mimetype=vnd.android.cursor.item/photo +870 data1=null +871 data2=null +872 data3=null +873 data4=null +874 data5=null +875 data6=null +876 data7=null +877 data8=null +878 data9=null +879 data10=null +880 data11=null +881 data12=null +882 data13=null +883 data14=null +884 data15= +885 is_primary=0 +886 is_super_primary=0 +887 data_version=0 +888 data_sync1=5c9ae978b346ac9 +889 data_sync2=null +890 data_sync3=null +891 data_sync4=null +892 } +893 33 { +894 _id=34 +895 raw_contact_id=2 +896 mimetype=vnd.android.cursor.item/group_membership +897 data1=1 +898 data2=null +899 data3=null +900 data4=null +901 data5=null +902 data6=null +903 data7=null +904 data8=null +905 data9=null +906 data10=null +907 data11=null +908 data12=null +909 data13=null +910 data14=null +911 data15=null +912 is_primary=0 +913 is_super_primary=0 +914 data_version=0 +915 data_sync1=null +916 data_sync2=null +917 data_sync3=null +918 data_sync4=null +919 } +920 34 { +921 _id=35 +922 raw_contact_id=3 +923 mimetype=vnd.android.cursor.item/group_membership +924 data1=1 +925 data2=null +926 data3=null +927 data4=null +928 data5=null +929 data6=null +930 data7=null +931 data8=null +932 data9=null +933 data10=null +934 data11=null +935 data12=null +936 data13=null +937 data14=null +938 data15=null +939 is_primary=0 +940 is_super_primary=0 +941 data_version=0 +942 data_sync1=null +943 data_sync2=null +944 data_sync3=null +945 data_sync4=null +946 } +947 35 { +948 _id=36 +949 raw_contact_id=3 +950 mimetype=vnd.android.cursor.item/group_membership +951 data1=2 +952 data2=null +953 data3=null +954 data4=null +955 data5=null +956 data6=null +957 data7=null +958 data8=null +959 data9=null +960 data10=null +961 data11=null +962 data12=null +963 data13=null +964 data14=null +965 data15=null +966 is_primary=0 +967 is_super_primary=0 +968 data_version=0 +969 data_sync1=null +970 data_sync2=null +971 data_sync3=null +972 data_sync4=null +973 } +974 36 { +975 _id=37 +976 raw_contact_id=3 +977 mimetype=vnd.android.cursor.item/group_membership +978 data1=4 +979 data2=null +980 data3=null +981 data4=null +982 data5=null +983 data6=null +984 data7=null +985 data8=null +986 data9=null +987 data10=null +988 data11=null +989 data12=null +990 data13=null +991 data14=null +992 data15=null +993 is_primary=0 +994 is_super_primary=0 +995 data_version=0 +996 data_sync1=null +997 data_sync2=null +998 data_sync3=null +999 data_sync4=null +1000 } +1001 37 { +1002 _id=38 +1003 raw_contact_id=11 +1004 mimetype=vnd.android.cursor.item/group_membership +1005 data1=1 +1006 data2=null +1007 data3=null +1008 data4=null +1009 data5=null +1010 data6=null +1011 data7=null +1012 data8=null +1013 data9=null +1014 data10=null +1015 data11=null +1016 data12=null +1017 data13=null +1018 data14=null +1019 data15=null +1020 is_primary=0 +1021 is_super_primary=0 +1022 data_version=0 +1023 data_sync1=null +1024 data_sync2=null +1025 data_sync3=null +1026 data_sync4=null +1027 } +1028 38 { +1029 _id=39 +1030 raw_contact_id=2 +1031 mimetype=vnd.android.cursor.item/group_membership +1032 data1=3 +1033 data2=null +1034 data3=null +1035 data4=null +1036 data5=null +1037 data6=null +1038 data7=null +1039 data8=null +1040 data9=null +1041 data10=null +1042 data11=null +1043 data12=null +1044 data13=null +1045 data14=null +1046 data15=null +1047 is_primary=0 +1048 is_super_primary=0 +1049 data_version=0 +1050 data_sync1=null +1051 data_sync2=null +1052 data_sync3=null +1053 data_sync4=null +1054 } diff --git a/tests/src/com/android/providers/contacts/ContactAggregatorTest.java b/tests/src/com/android/providers/contacts/ContactAggregatorTest.java index d7e96dc..842bdfc 100644 --- a/tests/src/com/android/providers/contacts/ContactAggregatorTest.java +++ b/tests/src/com/android/providers/contacts/ContactAggregatorTest.java @@ -16,10 +16,12 @@ package com.android.providers.contacts; import android.content.ContentUris; +import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract.AggregationExceptions; import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.test.suitebuilder.annotation.LargeTest; /** @@ -174,6 +176,55 @@ public class ContactAggregatorTest extends BaseContactsProvider2Test { assertAggregated(rawContactId1, rawContactId2, "H\u00e9l\u00e8ne Bj\u00f8rn"); } + public void testAggregationOfNumericNames() { + long rawContactId1 = createRawContact(); + insertStructuredName(rawContactId1, "123", null); + + long rawContactId2 = createRawContact(); + insertStructuredName(rawContactId2, "1-2-3", null); + + assertAggregated(rawContactId1, rawContactId2, "1-2-3"); + } + + public void testAggregationOfInconsistentlyParsedNames() { + long rawContactId1 = createRawContact(); + + ContentValues values = new ContentValues(); + values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave"); + values.put(StructuredName.GIVEN_NAME, "604"); + values.put(StructuredName.MIDDLE_NAME, "Arizona"); + values.put(StructuredName.FAMILY_NAME, "Ave"); + insertStructuredName(rawContactId1, values); + + long rawContactId2 = createRawContact(); + values.clear(); + values.put(StructuredName.DISPLAY_NAME, "604 Arizona Ave"); + values.put(StructuredName.GIVEN_NAME, "604"); + values.put(StructuredName.FAMILY_NAME, "Arizona Ave"); + insertStructuredName(rawContactId2, values); + + assertAggregated(rawContactId1, rawContactId2, "604 Arizona Ave"); + } + + public void testAggregationBasedOnMiddleName() { + ContentValues values = new ContentValues(); + long rawContactId1 = createRawContact(); + values.put(StructuredName.GIVEN_NAME, "John"); + values.put(StructuredName.GIVEN_NAME, "Abigale"); + values.put(StructuredName.FAMILY_NAME, "James"); + + insertStructuredName(rawContactId1, values); + + long rawContactId2 = createRawContact(); + values.clear(); + values.put(StructuredName.GIVEN_NAME, "John"); + values.put(StructuredName.GIVEN_NAME, "Marie"); + values.put(StructuredName.FAMILY_NAME, "James"); + insertStructuredName(rawContactId2, values); + + assertNotAggregated(rawContactId1, rawContactId2); + } + public void testAggregationBasedOnPhoneNumberNoNameData() { long rawContactId1 = createRawContact(); insertPhoneNumber(rawContactId1, "(888)555-1231"); diff --git a/tests/src/com/android/providers/contacts/LegacyContactImporterTest.java b/tests/src/com/android/providers/contacts/LegacyContactImporterTest.java index 91199f2..cab463d 100644 --- a/tests/src/com/android/providers/contacts/LegacyContactImporterTest.java +++ b/tests/src/com/android/providers/contacts/LegacyContactImporterTest.java @@ -144,7 +144,17 @@ public class LegacyContactImporterTest extends BaseContactsProvider2Test { Data.DATA2, Data.DATA3, Data.DATA4, + Data.DATA5, + Data.DATA6, + Data.DATA7, + Data.DATA8, Data.DATA9, + Data.DATA10, + Data.DATA11, + Data.DATA12, + Data.DATA13, + Data.DATA14, + Data.DATA15, Data.IS_PRIMARY, Data.IS_SUPER_PRIMARY, Data.DATA_VERSION, diff --git a/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java b/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java new file mode 100644 index 0000000..f345ae8 --- /dev/null +++ b/tests/src/com/android/providers/contacts/NameLookupBuilderTest.java @@ -0,0 +1,174 @@ +/* + * 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 android.test.suitebuilder.annotation.SmallTest; + +import java.util.Locale; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link NameLookupBuilder}. + */ +@SmallTest +public class NameLookupBuilderTest extends TestCase { + + private static class TestNameLookupBuilder extends NameLookupBuilder { + + StringBuilder sb = new StringBuilder(); + + public TestNameLookupBuilder(NameSplitter splitter) { + super(splitter); + } + + @Override + protected String normalizeName(String name) { + + // TO make the test more readable, simply return the name unnormalized + return name; + } + + @Override + protected String[] getCommonNicknameClusters(String normalizedName) { + if (normalizedName.equals("Bill")) { + return new String[] {"*William"}; + } else if (normalizedName.equals("Al")) { + return new String[] {"*Alex", "*Alice"}; + } + return null; + } + + public String inserted() { + return sb.toString(); + } + + @Override + protected void insertNameLookup(long rawContactId, long dataId, int lookupType, + String string) { + sb.append("(").append(lookupType).append(":").append(string).append(")"); + } + } + + private TestNameLookupBuilder mBuilder; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mBuilder = new TestNameLookupBuilder( + new NameSplitter("Mr", "", "", "", Locale.getDefault())); + } + + public void testEmptyName() { + mBuilder.insertNameLookup(0, 0, ""); + assertEquals("", mBuilder.inserted()); + } + + public void testSingleUniqueName() { + mBuilder.insertNameLookup(0, 0, "Foo"); + assertEquals("(0:Foo)(2:Foo)", mBuilder.inserted()); + } + + public void testSingleUniqueNameWithPrefix() { + mBuilder.insertNameLookup(0, 0, "Mr. Foo"); + assertEquals("(0:Foo)(2:Foo)", mBuilder.inserted()); + } + + public void testTwoUniqueNames() { + mBuilder.insertNameLookup(0, 0, "Foo Bar"); + assertEquals("(0:Foo.Bar)(2:FooBar)(1:Bar.Foo)(2:BarFoo)", mBuilder.inserted()); + } + + public void testThreeUniqueNames() { + mBuilder.insertNameLookup(0, 0, "Foo Bar Baz"); + + // All permutations + assertEquals( + "(0:Foo.Bar.Baz)(2:FooBarBaz)" + + "(1:Foo.Baz.Bar)(2:FooBazBar)" + + + "(1:Bar.Foo.Baz)(2:BarFooBaz)" + + "(1:Bar.Baz.Foo)(2:BarBazFoo)" + + + "(1:Baz.Bar.Foo)(2:BazBarFoo)" + + "(1:Baz.Foo.Bar)(2:BazFooBar)", mBuilder.inserted()); + } + + public void testFourUniqueNames() { + mBuilder.insertNameLookup(0, 0, "Foo Bar Baz Biz"); + + // All permutations + assertEquals( + "(0:Foo.Bar.Baz.Biz)(2:FooBarBazBiz)" + + "(1:Foo.Bar.Biz.Baz)(2:FooBarBizBaz)" + + "(1:Foo.Baz.Bar.Biz)(2:FooBazBarBiz)" + + "(1:Foo.Baz.Biz.Bar)(2:FooBazBizBar)" + + "(1:Foo.Biz.Baz.Bar)(2:FooBizBazBar)" + + "(1:Foo.Biz.Bar.Baz)(2:FooBizBarBaz)" + + + "(1:Bar.Foo.Baz.Biz)(2:BarFooBazBiz)" + + "(1:Bar.Foo.Biz.Baz)(2:BarFooBizBaz)" + + "(1:Bar.Baz.Foo.Biz)(2:BarBazFooBiz)" + + "(1:Bar.Baz.Biz.Foo)(2:BarBazBizFoo)" + + "(1:Bar.Biz.Baz.Foo)(2:BarBizBazFoo)" + + "(1:Bar.Biz.Foo.Baz)(2:BarBizFooBaz)" + + + "(1:Baz.Bar.Foo.Biz)(2:BazBarFooBiz)" + + "(1:Baz.Bar.Biz.Foo)(2:BazBarBizFoo)" + + "(1:Baz.Foo.Bar.Biz)(2:BazFooBarBiz)" + + "(1:Baz.Foo.Biz.Bar)(2:BazFooBizBar)" + + "(1:Baz.Biz.Foo.Bar)(2:BazBizFooBar)" + + "(1:Baz.Biz.Bar.Foo)(2:BazBizBarFoo)" + + + "(1:Biz.Bar.Baz.Foo)(2:BizBarBazFoo)" + + "(1:Biz.Bar.Foo.Baz)(2:BizBarFooBaz)" + + "(1:Biz.Baz.Bar.Foo)(2:BizBazBarFoo)" + + "(1:Biz.Baz.Foo.Bar)(2:BizBazFooBar)" + + "(1:Biz.Foo.Baz.Bar)(2:BizFooBazBar)" + + "(1:Biz.Foo.Bar.Baz)(2:BizFooBarBaz)", mBuilder.inserted()); + } + + public void testSingleNickname() { + mBuilder.insertNameLookup(0, 0, "Bill"); + assertEquals("(0:Bill)(2:Bill)(1:*William)", mBuilder.inserted()); + } + + public void testSingleNameWithTwoNicknames() { + mBuilder.insertNameLookup(0, 0, "Al"); + assertEquals("(0:Al)(2:Al)(1:*Alex)(1:*Alice)", mBuilder.inserted()); + } + + public void testTwoNamesOneOfWhichIsNickname() { + mBuilder.insertNameLookup(0, 0, "Foo Al"); + assertEquals( + "(0:Foo.Al)(2:FooAl)" + + "(1:Al.Foo)(2:AlFoo)" + + "(1:Foo.*Alex)(1:*Alex.Foo)" + + "(1:Foo.*Alice)(1:*Alice.Foo)", mBuilder.inserted()); + } + + public void testTwoNamesBothNickname() { + mBuilder.insertNameLookup(0, 0, "Bill Al"); + assertEquals( + "(0:Bill.Al)(2:BillAl)" + + "(1:Al.Bill)(2:AlBill)" + + "(1:*William.Al)(1:Al.*William)" + + "(1:*William.*Alex)(1:*Alex.*William)" + + "(1:*William.*Alice)(1:*Alice.*William)" + + "(1:Bill.*Alex)(1:*Alex.Bill)" + + "(1:Bill.*Alice)(1:*Alice.Bill)", mBuilder.inserted()); + } +} -- cgit v1.1