[Issue 2072020] Updating name lookup tables when structured name, email, nickname are updated.

This is also an optimization: we will no longer be rebuilding
name lookup structures over and over again as a contact gets updated
and aggregated.
diff --git a/src/com/android/providers/contacts/ContactAggregator.java b/src/com/android/providers/contacts/ContactAggregator.java
index c4b4ad0..9f4b057 100644
--- a/src/com/android/providers/contacts/ContactAggregator.java
+++ b/src/com/android/providers/contacts/ContactAggregator.java
@@ -343,12 +343,6 @@
 
         mOpenHelper.removeContactIfSingleton(rawContactId);
 
-        // TODO compiled statements
-
-        // Clear out data used for aggregation - we will recreate it during aggregation
-        db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + " WHERE "
-                + NameLookupColumns.RAW_CONTACT_ID + "=" + rawContactId);
-
         // Clear out the contact ID field on the contact
         ContentValues values = new ContentValues();
         values.putNull(RawContacts.CONTACT_ID);
@@ -400,7 +394,6 @@
         }
 
         mOpenHelper.updateContactVisible(contactId);
-        updateContactAggregationData(db, rawContactId, candidates, values);
     }
 
     /**
@@ -606,6 +599,7 @@
     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);
@@ -614,8 +608,6 @@
                 candidates.add(clusters[i], NameLookupType.NAME_VARIANT);
             }
         }
-
-        candidates.add(nameN, NameLookupType.NAME_COLLATION_KEY);
     }
 
     private void addMatchCandidatesFullName(String givenName, String familyName, int mode,
@@ -625,10 +617,14 @@
         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);
@@ -636,10 +632,10 @@
             for (int i = 0; i < familyNameNicknames.length; i++) {
                 candidates.add(familyNameNicknames[i] + "." + givenNameN,
                         NameLookupType.NAME_VARIANT);
+                candidates.add(givenNameN + "." + familyNameNicknames[i],
+                        NameLookupType.NAME_VARIANT);
             }
         }
-        candidates.add(givenNameN + familyNameN, NameLookupType.NAME_COLLATION_KEY);
-        candidates.add(familyNameN + givenNameN, NameLookupType.NAME_COLLATION_KEY);
     }
 
     /**
@@ -681,6 +677,7 @@
         }
 
         StringBuilder selection = new StringBuilder();
+        selection.append(RawContacts.CONTACT_ID + " NOT NULL AND ");
         selection.append(NameLookupColumns.NORMALIZED_NAME);
         selection.append(" IN (");
         for (int i = 0; i < candidates.mCount; i++) {
@@ -798,46 +795,6 @@
         }
     }
 
-    /**
-     * Prepares the supplied contact for aggregation with other contacts by (re)computing
-     * match lookup keys.
-     */
-    private void updateContactAggregationData(SQLiteDatabase db, long rawContactId,
-            MatchCandidateList candidates, ContentValues values) {
-        candidates.clear();
-
-        final Cursor c = db.query(Tables.DATA_JOIN_MIMETYPES,
-                DATA_JOIN_MIMETYPE_COLUMNS,
-                DatabaseUtils.concatenateWhere(Data.RAW_CONTACT_ID + "=" + rawContactId,
-                        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);
-                if (mimeType.equals(CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)) {
-                    addMatchCandidatesStructuredName(data1, data2, MODE_INSERT_LOOKUP_DATA,
-                            candidates);
-                } else if (mimeType.equals(CommonDataKinds.Email.CONTENT_ITEM_TYPE)) {
-                    addMatchCandidatesEmail(data2, MODE_INSERT_LOOKUP_DATA, candidates);
-                } else if (mimeType.equals(CommonDataKinds.Nickname.CONTENT_ITEM_TYPE)) {
-                    addMatchCandidatesNickname(data2, MODE_INSERT_LOOKUP_DATA, candidates);
-                }
-            }
-        } finally {
-            c.close();
-        }
-
-        db.execSQL("DELETE FROM " + Tables.NAME_LOOKUP + " WHERE "
-                + NameLookupColumns.RAW_CONTACT_ID + "=" + rawContactId + ";");
-        for (int i = 0; i < candidates.mCount; i++) {
-            NameMatchCandidate candidate = candidates.mList.get(i);
-            mOpenHelper.insertNameLookup(rawContactId, candidate.mLookupType, candidate.mName);
-        }
-    }
-
     private interface RawContactsQuery {
         String[] COLUMNS = new String[] {
             RawContactsColumns.CONCRETE_ID,
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 56f9f5a..82523e6 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -209,7 +209,7 @@
         public static final int DISPLAY_NAME = 3;
     }
 
-    private interface DataQuery {
+    private interface DataDeleteQuery {
         public static final String TABLE = Tables.DATA_JOIN_MIMETYPES;
 
         public static final String[] CONCRETE_COLUMNS = new String[] {
@@ -228,14 +228,14 @@
             Data.DATA2,
         };
 
-        public static final int ID = 0;
+        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;
     }
 
-    private interface DataIdQuery {
+    private interface DataUpdateQuery {
         String[] COLUMNS = { Data._ID, Data.RAW_CONTACT_ID, Data.MIMETYPE };
 
         int _ID = 0;
@@ -616,7 +616,6 @@
                 setIsPrimary(rawContactId, dataId, getMimeTypeId());
             }
 
-            fixContactDisplayName(db, rawContactId);
             return dataId;
         }
 
@@ -626,8 +625,8 @@
          */
         public void update(SQLiteDatabase db, ContentValues values, Cursor c,
                 boolean markRawContactAsDirty) {
-            long dataId = c.getLong(DataIdQuery._ID);
-            long rawContactId = c.getLong(DataIdQuery.RAW_CONTACT_ID);
+            long dataId = c.getLong(DataUpdateQuery._ID);
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
 
             if (values.containsKey(Data.IS_SUPER_PRIMARY)) {
                 long mimeTypeId = getMimeTypeId();
@@ -648,21 +647,18 @@
                 mDb.update(Tables.DATA, values, Data._ID + " = " + dataId, null);
             }
 
-            fixContactDisplayName(db, rawContactId);
-
             if (markRawContactAsDirty) {
                 setRawContactDirty(rawContactId);
             }
         }
 
         public int delete(SQLiteDatabase db, Cursor c) {
-            long dataId = c.getLong(DataQuery.ID);
-            long rawContactId = c.getLong(DataQuery.RAW_CONTACT_ID);
-            boolean primary = c.getInt(DataQuery.IS_PRIMARY) != 0;
+            long dataId = c.getLong(DataDeleteQuery._ID);
+            long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+            boolean primary = c.getInt(DataDeleteQuery.IS_PRIMARY) != 0;
             int count = db.delete(Tables.DATA, Data._ID + "=" + dataId, null);
             if (count != 0 && primary) {
                 fixPrimary(db, rawContactId);
-                fixContactDisplayName(db, rawContactId);
             }
             return count;
         }
@@ -670,7 +666,7 @@
         private void fixPrimary(SQLiteDatabase db, long rawContactId) {
             long newPrimaryId = findNewPrimaryDataId(db, rawContactId);
             if (newPrimaryId != -1) {
-                setIsPrimary(newPrimaryId, rawContactId, getMimeTypeId());
+                setIsPrimary(rawContactId, newPrimaryId, getMimeTypeId());
             }
         }
 
@@ -680,8 +676,8 @@
             Cursor c = queryData(db, rawContactId);
             try {
                 while (c.moveToNext()) {
-                    long dataId = c.getLong(DataQuery.ID);
-                    int type = c.getInt(DataQuery.DATA2);
+                    long dataId = c.getLong(DataDeleteQuery._ID);
+                    int type = c.getInt(DataDeleteQuery.DATA2);
                     if (primaryType == -1 || getTypeRank(type) < getTypeRank(primaryType)) {
                         primaryId = dataId;
                         primaryType = type;
@@ -702,16 +698,13 @@
         }
 
         protected Cursor queryData(SQLiteDatabase db, long rawContactId) {
-            return db.query(DataQuery.TABLE, DataQuery.CONCRETE_COLUMNS, Data.RAW_CONTACT_ID + "="
-                    + rawContactId + " AND " + MimetypesColumns.MIMETYPE + "='" + mMimetype + "'",
+            return db.query(DataDeleteQuery.TABLE, DataDeleteQuery.CONCRETE_COLUMNS,
+                    Data.RAW_CONTACT_ID + "=" + rawContactId +
+                    " AND " + MimetypesColumns.MIMETYPE + "='" + mMimetype + "'",
                     null, null, null, null);
         }
 
         protected void fixContactDisplayName(SQLiteDatabase db, long rawContactId) {
-            if (!sDisplayNamePriorities.containsKey(mMimetype)) {
-                return;
-            }
-
             String bestDisplayName = null;
             Cursor c = db.query(DisplayNameQuery.TABLE, DisplayNameQuery.COLUMNS,
                     Data.RAW_CONTACT_ID + "=" + rawContactId, null, null, null, null);
@@ -744,6 +737,8 @@
             }
 
             setDisplayName(rawContactId, bestDisplayName);
+
+            // TODO also fix the aggregate contact display name right away
         }
     }
 
@@ -766,7 +761,68 @@
         @Override
         public long insert(SQLiteDatabase db, long rawContactId, ContentValues values) {
             fixStructuredNameComponents(values);
-            return super.insert(db, rawContactId, values);
+
+            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);
+            fixContactDisplayName(db, rawContactId);
+            return dataId;
+        }
+
+        @Override
+        public void update(SQLiteDatabase db, ContentValues values, Cursor c,
+                boolean markRawContactAsDirty) {
+            long dataId = c.getLong(DataUpdateQuery._ID);
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
+
+            super.update(db, values, c, markRawContactAsDirty);
+
+            boolean hasGivenName = values.containsKey(StructuredName.GIVEN_NAME);
+            boolean hasFamilyName = values.containsKey(StructuredName.FAMILY_NAME);
+            if  (hasGivenName || hasFamilyName) {
+                String givenName;
+                String familyName;// = values.getAsString(StructuredName.FAMILY_NAME);
+                if (hasGivenName) {
+                    givenName = values.getAsString(StructuredName.GIVEN_NAME);
+                } else {
+
+                    // TODO compiled statement
+                    givenName = DatabaseUtils.stringForQuery(db,
+                            "SELECT " + StructuredName.GIVEN_NAME +
+                            " FROM " + Tables.DATA +
+                            " WHERE " + Data._ID + "=" + dataId, null);
+                }
+                if (hasFamilyName) {
+                    familyName = values.getAsString(StructuredName.FAMILY_NAME);
+                } else {
+
+                    // TODO compiled statement
+                    familyName = DatabaseUtils.stringForQuery(db,
+                            "SELECT " + StructuredName.FAMILY_NAME +
+                            " FROM " + Tables.DATA +
+                            " WHERE " + Data._ID + "=" + dataId, null);
+                }
+
+                mOpenHelper.deleteNameLookup(dataId);
+                mOpenHelper.insertNameLookupForStructuredName(rawContactId, dataId, givenName,
+                        familyName);
+            }
+            fixContactDisplayName(db, rawContactId);
+        }
+
+        @Override
+        public int delete(SQLiteDatabase db, Cursor c) {
+            long dataId = c.getLong(DataDeleteQuery._ID);
+            long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+
+            int count = super.delete(db, c);
+
+            mOpenHelper.deleteNameLookup(dataId);
+            fixContactDisplayName(db, rawContactId);
+            return count;
         }
 
         /**
@@ -867,6 +923,25 @@
         }
 
         @Override
+        public void update(SQLiteDatabase db, ContentValues values, Cursor c,
+                boolean markRawContactAsDirty) {
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
+
+            super.update(db, values, c, markRawContactAsDirty);
+
+            fixContactDisplayName(db, rawContactId);
+        }
+
+        @Override
+        public int delete(SQLiteDatabase db, Cursor c) {
+            long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+
+            int count = super.delete(db, c);
+            fixContactDisplayName(db, rawContactId);
+            return count;
+        }
+
+        @Override
         protected int getTypeRank(int type) {
             switch (type) {
                 case Organization.TYPE_WORK: return 0;
@@ -885,9 +960,39 @@
 
         @Override
         public long insert(SQLiteDatabase db, long rawContactId, ContentValues values) {
-            long id = super.insert(db, rawContactId, values);
+            String address = values.getAsString(Email.DATA);
+
+            long dataId = super.insert(db, rawContactId, values);
+
             fixContactDisplayName(db, rawContactId);
-            return id;
+            mOpenHelper.insertNameLookupForEmail(rawContactId, dataId, address);
+            return dataId;
+        }
+
+        @Override
+        public void update(SQLiteDatabase db, ContentValues values, Cursor c,
+                boolean markRawContactAsDirty) {
+            long dataId = c.getLong(DataUpdateQuery._ID);
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
+            String address = values.getAsString(Email.DATA);
+
+            super.update(db, values, c, markRawContactAsDirty);
+
+            mOpenHelper.deleteNameLookup(dataId);
+            mOpenHelper.insertNameLookupForEmail(rawContactId, dataId, address);
+            fixContactDisplayName(db, rawContactId);
+        }
+
+        @Override
+        public int delete(SQLiteDatabase db, Cursor c) {
+            long dataId = c.getLong(DataDeleteQuery._ID);
+            long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+
+            int count = super.delete(db, c);
+
+            mOpenHelper.deleteNameLookup(dataId);
+            fixContactDisplayName(db, rawContactId);
+            return count;
         }
 
         @Override
@@ -902,6 +1007,50 @@
         }
     }
 
+    public class NicknameDataRowHandler extends CommonDataRowHandler {
+
+        public NicknameDataRowHandler() {
+            super(Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE, Nickname.LABEL);
+        }
+
+        @Override
+        public long insert(SQLiteDatabase db, long rawContactId, ContentValues values) {
+            String nickname = values.getAsString(Nickname.NAME);
+
+            long dataId = super.insert(db, rawContactId, values);
+
+            fixContactDisplayName(db, rawContactId);
+            mOpenHelper.insertNameLookupForNickname(rawContactId, dataId, nickname);
+            return dataId;
+        }
+
+        @Override
+        public void update(SQLiteDatabase db, ContentValues values, Cursor c,
+                boolean markRawContactAsDirty) {
+            long dataId = c.getLong(DataUpdateQuery._ID);
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
+            String nickname = values.getAsString(Nickname.NAME);
+
+            super.update(db, values, c, markRawContactAsDirty);
+
+            mOpenHelper.deleteNameLookup(dataId);
+            mOpenHelper.insertNameLookupForNickname(rawContactId, dataId, nickname);
+            fixContactDisplayName(db, rawContactId);
+        }
+
+        @Override
+        public int delete(SQLiteDatabase db, Cursor c) {
+            long dataId = c.getLong(DataDeleteQuery._ID);
+            long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+
+            int count = super.delete(db, c);
+
+            mOpenHelper.deleteNameLookup(dataId);
+            fixContactDisplayName(db, rawContactId);
+            return count;
+        }
+    }
+
     public class PhoneDataRowHandler extends CommonDataRowHandler {
 
         public PhoneDataRowHandler() {
@@ -916,20 +1065,34 @@
             long dataId = super.insert(db, rawContactId, values);
 
             updatePhoneLookup(db, rawContactId, dataId, number, normalizedNumber);
+            fixContactDisplayName(db, rawContactId);
             return dataId;
         }
 
         @Override
         public void update(SQLiteDatabase db, ContentValues values, Cursor c,
                 boolean markRawContactAsDirty) {
+            long dataId = c.getLong(DataUpdateQuery._ID);
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
             String number = values.getAsString(Phone.NUMBER);
             String normalizedNumber = computeNormalizedNumber(number, values);
 
             super.update(db, values, c, markRawContactAsDirty);
 
-            long dataId = c.getLong(DataIdQuery._ID);
-            long rawContactId = c.getLong(DataIdQuery.RAW_CONTACT_ID);
             updatePhoneLookup(db, rawContactId, dataId, number, normalizedNumber);
+            fixContactDisplayName(db, rawContactId);
+        }
+
+        @Override
+        public int delete(SQLiteDatabase db, Cursor c) {
+            long dataId = c.getLong(DataDeleteQuery._ID);
+            long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+
+            int count = super.delete(db, c);
+
+            updatePhoneLookup(db, rawContactId, dataId, null, null);
+            fixContactDisplayName(db, rawContactId);
+            return count;
         }
 
         private String computeNormalizedNumber(String number, ContentValues values) {
@@ -985,7 +1148,7 @@
         @Override
         public void update(SQLiteDatabase db, ContentValues values, Cursor c,
                 boolean markRawContactAsDirty) {
-            long rawContactId = c.getLong(DataQuery.RAW_CONTACT_ID);
+            long rawContactId = c.getLong(DataUpdateQuery.RAW_CONTACT_ID);
             resolveGroupSourceIdInValues(rawContactId, db, values, false);
             super.update(db, values, c, markRawContactAsDirty);
         }
@@ -1116,8 +1279,7 @@
                 StructuredPostal.CONTENT_ITEM_TYPE, StructuredPostal.TYPE, StructuredPostal.LABEL));
         mDataRowHandlers.put(Organization.CONTENT_ITEM_TYPE, new OrganizationDataRowHandler());
         mDataRowHandlers.put(Phone.CONTENT_ITEM_TYPE, new PhoneDataRowHandler());
-        mDataRowHandlers.put(Nickname.CONTENT_ITEM_TYPE, new CommonDataRowHandler(
-                Nickname.CONTENT_ITEM_TYPE, Nickname.TYPE, Nickname.LABEL));
+        mDataRowHandlers.put(Nickname.CONTENT_ITEM_TYPE, new NicknameDataRowHandler());
         mDataRowHandlers.put(StructuredName.CONTENT_ITEM_TYPE,
                 new StructuredNameRowHandler(mNameSplitter));
         mDataRowHandlers.put(GroupMembership.CONTENT_ITEM_TYPE,
@@ -1533,11 +1695,11 @@
 
         // Note that the query will return data according to the access restrictions,
         // so we don't need to worry about deleting data we don't have permission to read.
-        Cursor c = query(Data.CONTENT_URI, DataQuery.COLUMNS, selection, selectionArgs, null);
+        Cursor c = query(Data.CONTENT_URI, DataDeleteQuery.COLUMNS, selection, selectionArgs, null);
         try {
             while(c.moveToNext()) {
-                long rawContactId = c.getLong(DataQuery.RAW_CONTACT_ID);
-                String mimeType = c.getString(DataQuery.MIMETYPE);
+                long rawContactId = c.getLong(DataDeleteQuery.RAW_CONTACT_ID);
+                String mimeType = c.getString(DataDeleteQuery.MIMETYPE);
                 count += getDataRowHandler(mimeType).delete(mDb, c);
                 if (markRawContactAsDirty) {
                     setRawContactDirty(rawContactId);
@@ -1557,14 +1719,15 @@
 
         // Note that the query will return data according to the access restrictions,
         // so we don't need to worry about deleting data we don't have permission to read.
-        Cursor c = query(Data.CONTENT_URI, DataQuery.COLUMNS, Data._ID + "=" + dataId, null, null);
+        Cursor c = query(Data.CONTENT_URI, DataDeleteQuery.COLUMNS, Data._ID + "=" + dataId, null,
+                null);
 
         try {
             if (!c.moveToFirst()) {
                 return 0;
             }
 
-            String mimeType = c.getString(DataQuery.MIMETYPE);
+            String mimeType = c.getString(DataDeleteQuery.MIMETYPE);
             boolean valid = false;
             for (int i = 0; i < allowedMimeTypes.length; i++) {
                 if (TextUtils.equals(mimeType, allowedMimeTypes[i])) {
@@ -1855,7 +2018,7 @@
         // TODO delete aggregation exceptions
         mOpenHelper.removeContactIfSingleton(rawContactId);
         if (permanently) {
-            mDb.delete(Tables.PRESENCE, Presence.RAW_CONTACT_ID + "=" + rawContactId, null);
+            mDb.delete(Tables.PRESENCE, PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null);
             return mDb.delete(Tables.RAW_CONTACTS, RawContacts._ID + "=" + rawContactId, null);
         } else {
 
@@ -2030,7 +2193,7 @@
 
         // Note that the query will return data according to the access restrictions,
         // so we don't need to worry about updating data we don't have permission to read.
-        Cursor c = query(uri, DataIdQuery.COLUMNS, selection, selectionArgs, null);
+        Cursor c = query(uri, DataUpdateQuery.COLUMNS, selection, selectionArgs, null);
         try {
             while(c.moveToNext()) {
                 count += updateData(mValues, c, markRawContactAsDirty);
@@ -2047,7 +2210,7 @@
             return 0;
         }
 
-        final String mimeType = c.getString(DataIdQuery.MIMETYPE);
+        final String mimeType = c.getString(DataUpdateQuery.MIMETYPE);
         getDataRowHandler(mimeType).update(mDb, values, c, markRawContactAsDirty);
         return 1;
     }
diff --git a/src/com/android/providers/contacts/LegacyContactImporter.java b/src/com/android/providers/contacts/LegacyContactImporter.java
index 712bcf3..dbc6f19 100644
--- a/src/com/android/providers/contacts/LegacyContactImporter.java
+++ b/src/com/android/providers/contacts/LegacyContactImporter.java
@@ -64,6 +64,7 @@
 
     private final Context mContext;
     private final ContactsProvider2 mContactsProvider;
+    private OpenHelper mOpenHelper;
     private ContentValues mValues = new ContentValues();
     private ContentResolver mResolver;
     private boolean mPhoneticNameAvailable = true;
@@ -86,6 +87,7 @@
     private long mPhotoMimetypeId;
     private long mGroupMembershipMimetypeId;
 
+
     public LegacyContactImporter(Context context, ContactsProvider2 contactsProvider) {
         mContext = context;
         mContactsProvider = contactsProvider;
@@ -137,8 +139,8 @@
             mPhoneticNameAvailable = false;
         }
 
-        OpenHelper openHelper = (OpenHelper)mContactsProvider.getOpenHelper();
-        mTargetDb = openHelper.getWritableDatabase();
+        mOpenHelper = (OpenHelper)mContactsProvider.getOpenHelper();
+        mTargetDb = mOpenHelper.getWritableDatabase();
 
         /*
          * At this point there should be no data in the contacts provider, but in case
@@ -148,16 +150,16 @@
          */
         mContactsProvider.wipeData();
 
-        mStructuredNameMimetypeId = openHelper.getMimeTypeId(StructuredName.CONTENT_ITEM_TYPE);
-        mNoteMimetypeId = openHelper.getMimeTypeId(Note.CONTENT_ITEM_TYPE);
-        mOrganizationMimetypeId = openHelper.getMimeTypeId(Organization.CONTENT_ITEM_TYPE);
-        mPhoneMimetypeId = openHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE);
-        mEmailMimetypeId = openHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
-        mImMimetypeId = openHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
-        mPostalMimetypeId = openHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE);
-        mPhotoMimetypeId = openHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
+        mStructuredNameMimetypeId = mOpenHelper.getMimeTypeId(StructuredName.CONTENT_ITEM_TYPE);
+        mNoteMimetypeId = mOpenHelper.getMimeTypeId(Note.CONTENT_ITEM_TYPE);
+        mOrganizationMimetypeId = mOpenHelper.getMimeTypeId(Organization.CONTENT_ITEM_TYPE);
+        mPhoneMimetypeId = mOpenHelper.getMimeTypeId(Phone.CONTENT_ITEM_TYPE);
+        mEmailMimetypeId = mOpenHelper.getMimeTypeId(Email.CONTENT_ITEM_TYPE);
+        mImMimetypeId = mOpenHelper.getMimeTypeId(Im.CONTENT_ITEM_TYPE);
+        mPostalMimetypeId = mOpenHelper.getMimeTypeId(StructuredPostal.CONTENT_ITEM_TYPE);
+        mPhotoMimetypeId = mOpenHelper.getMimeTypeId(Photo.CONTENT_ITEM_TYPE);
         mGroupMembershipMimetypeId =
-                openHelper.getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
+                mOpenHelper.getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE);
 
         mNameSplitter = mContactsProvider.getNameSplitter();
 
@@ -487,10 +489,13 @@
         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, splitName.getGivenNames());
+        bindString(insert, StructuredNameInsert.GIVEN_NAME, givenNames);
         bindString(insert, StructuredNameInsert.MIDDLE_NAME, splitName.getMiddleName());
-        bindString(insert, StructuredNameInsert.FAMILY_NAME, splitName.getFamilyName());
+        bindString(insert, StructuredNameInsert.FAMILY_NAME, familyName);
         bindString(insert, StructuredNameInsert.SUFFIX, splitName.getSuffix());
 
         if (mPhoneticNameAvailable) {
@@ -498,7 +503,9 @@
             String phoneticName = c.getString(PeopleQuery.PHONETIC_NAME);
         }
 
-        insert(insert);
+        long dataId = insert(insert);
+
+        mOpenHelper.insertNameLookupForStructuredName(id, dataId, givenNames, familyName);
     }
 
     private void insertNote(Cursor c, SQLiteStatement insert) {
@@ -682,15 +689,18 @@
 
     private void insertEmail(Cursor c, SQLiteStatement insert) {
         long personId = c.getLong(ContactMethodsQuery.PERSON);
+        String email = c.getString(ContactMethodsQuery.DATA);
 
         insert.bindLong(EmailInsert.RAW_CONTACT_ID, personId);
         insert.bindLong(EmailInsert.MIMETYPE_ID, mEmailMimetypeId);
         bindString(insert, EmailInsert.IS_PRIMARY, c.getString(ContactMethodsQuery.ISPRIMARY));
-        bindString(insert, EmailInsert.DATA, c.getString(ContactMethodsQuery.DATA));
+        bindString(insert, EmailInsert.DATA, email);
         bindString(insert, EmailInsert.AUX_DATA, c.getString(ContactMethodsQuery.AUX_DATA));
         bindString(insert, EmailInsert.TYPE, c.getString(ContactMethodsQuery.TYPE));
         bindString(insert, EmailInsert.LABEL, c.getString(ContactMethodsQuery.LABEL));
-        insert(insert);
+
+        long dataId = insert(insert);
+        mOpenHelper.insertNameLookupForEmail(personId, dataId, email);
     }
 
     private void insertIm(Cursor c, SQLiteStatement insert) {
diff --git a/src/com/android/providers/contacts/OpenHelper.java b/src/com/android/providers/contacts/OpenHelper.java
index 14362d5..82ee58d 100644
--- a/src/com/android/providers/contacts/OpenHelper.java
+++ b/src/com/android/providers/contacts/OpenHelper.java
@@ -45,6 +45,9 @@
 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;
@@ -354,8 +357,8 @@
     }
 
     public interface NameLookupColumns {
-        public static final String _ID = BaseColumns._ID;
         public static final String RAW_CONTACT_ID = "raw_contact_id";
+        public static final String DATA_ID = "data_id";
         public static final String NORMALIZED_NAME = "normalized_name";
         public static final String NAME_TYPE = "name_type";
     }
@@ -439,6 +442,7 @@
     private SQLiteStatement mMimetypeInsert;
     private SQLiteStatement mPackageInsert;
     private SQLiteStatement mNameLookupInsert;
+    private SQLiteStatement mNameLookupDelete;
 
     private SQLiteStatement mDataMimetypeQuery;
     private SQLiteStatement mActivitiesMimetypeQuery;
@@ -508,8 +512,11 @@
                 + " 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.NAME_TYPE + ","
-                + NameLookupColumns.NORMALIZED_NAME + ") VALUES (?,?,?)");
+                + 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 "
@@ -732,11 +739,14 @@
 
         // Private name/nickname table used for lookup
         db.execSQL("CREATE TABLE " + Tables.NAME_LOOKUP + " (" +
+                NameLookupColumns.DATA_ID
+                        + " INTEGER REFERENCES data(_id) NOT NULL," +
                 NameLookupColumns.RAW_CONTACT_ID
                         + " INTEGER REFERENCES raw_contacts(_id) NOT NULL," +
                 NameLookupColumns.NORMALIZED_NAME + " TEXT NOT NULL," +
                 NameLookupColumns.NAME_TYPE + " INTEGER NOT NULL," +
-                "PRIMARY KEY (" + NameLookupColumns.RAW_CONTACT_ID + ", "
+                "PRIMARY KEY ("
+                        + NameLookupColumns.DATA_ID + ", "
                         + NameLookupColumns.NORMALIZED_NAME + ", "
                         + NameLookupColumns.NAME_TYPE + ")" +
         ");");
@@ -747,6 +757,10 @@
                 NameLookupColumns.RAW_CONTACT_ID +
         ");");
 
+        db.execSQL("CREATE INDEX name_lookup_raw_contact_id_index ON " + Tables.NAME_LOOKUP + " (" +
+                NameLookupColumns.RAW_CONTACT_ID +
+        ");");
+
         db.execSQL("CREATE TABLE " + Tables.NICKNAME_LOOKUP + " (" +
                 NicknameLookupColumns.NAME + " TEXT," +
                 NicknameLookupColumns.CLUSTER + " TEXT" +
@@ -1277,16 +1291,118 @@
     }
 
     /**
+     * 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);
+        mNameLookupInsert.execute();
+    }
+
+    /**
      * Inserts a record in the {@link Tables#NAME_LOOKUP} table.
      */
-    public void insertNameLookup(long rawContactId, int lookupType, String name) {
+    public void insertNameLookup(long rawContactId, long dataId, int lookupType, String name) {
         getWritableDatabase();
         DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 1, rawContactId);
-        DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 2, lookupType);
-        DatabaseUtils.bindObjectToProgram(mNameLookupInsert, 3, name);
+        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) {
+        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) {
+        insertNameLookup(rawContactId, dataId,
+                NameLookupType.NICKNAME, NameNormalizer.normalize(nickname));
+    }
+
     public void buildPhoneLookupAndRawContactQuery(SQLiteQueryBuilder qb, String number) {
         String normalizedNumber = PhoneNumberUtils.toCallerIDMinMatch(number);
         StringBuilder sb = new StringBuilder();