summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitri Plotnikov <dplotnikov@google.com>2009-09-20 16:56:40 -0700
committerDmitri Plotnikov <dplotnikov@google.com>2009-09-21 09:49:52 -0700
commitf23764675b35b5262a39c79aad8e9842460274b2 (patch)
tree7c5da61a6713912dc6ace1777e15760169526211
parentd15a13a9b1dfe59dcf7196c6d5d55afe7f25d2a2 (diff)
downloadpackages_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
-rw-r--r--src/com/android/providers/contacts/ContactAggregator.java199
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java199
-rw-r--r--src/com/android/providers/contacts/GlobalSearchSupport.java16
-rw-r--r--src/com/android/providers/contacts/LegacyApiSupport.java6
-rw-r--r--src/com/android/providers/contacts/LegacyContactImporter.java11
-rw-r--r--src/com/android/providers/contacts/NameLookupBuilder.java166
-rw-r--r--src/com/android/providers/contacts/NameNormalizer.java9
-rw-r--r--src/com/android/providers/contacts/NameSplitter.java35
-rw-r--r--src/com/android/providers/contacts/OpenHelper.java199
-rw-r--r--tests/assets/expected_data.txt1687
-rw-r--r--tests/src/com/android/providers/contacts/ContactAggregatorTest.java51
-rw-r--r--tests/src/com/android/providers/contacts/LegacyContactImporterTest.java10
-rw-r--r--tests/src/com/android/providers/contacts/NameLookupBuilderTest.java174
13 files changed, 1756 insertions, 1006 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)) {
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=<unprintable>
-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=<unprintable>
-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=<unprintable>
+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=<unprintable>
+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());
+ }
+}