Merge "Modifications to bold contacts' first names."
diff --git a/res/layout/contact_detail_header_view.xml b/res/layout/contact_detail_header_view.xml
index 80dcacc..328a5ff 100644
--- a/res/layout/contact_detail_header_view.xml
+++ b/res/layout/contact_detail_header_view.xml
@@ -54,7 +54,6 @@
             android:gravity="bottom"
             android:textSize="@dimen/detail_header_name_text_size"
             android:textColor="@color/detail_header_view_text_color"
-            android:textStyle="bold"
          />
 
         <TextView android:id="@+id/phonetic_name"
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index f4baf3b..4b0bbaa 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -93,6 +93,7 @@
         private final long mPhotoId;
         private final String mPhotoUri;
         private final String mDisplayName;
+        private final String mAltDisplayName;
         private final String mPhoneticName;
         private final boolean mStarred;
         private final Integer mPresence;
@@ -131,6 +132,7 @@
             mPhotoId = -1;
             mPhotoUri = null;
             mDisplayName = null;
+            mAltDisplayName = null;
             mPhoneticName = null;
             mStarred = false;
             mPresence = null;
@@ -145,8 +147,9 @@
          */
         private Result(Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id,
                 long nameRawContactId, int displayNameSource, long photoId, String photoUri,
-                String displayName, String phoneticName, boolean starred, Integer presence,
-                String status, Long statusTimestamp, Integer statusLabel, String statusResPackage) {
+                String displayName, String altDisplayName, String phoneticName, boolean starred,
+                Integer presence, String status, Long statusTimestamp, Integer statusLabel,
+                String statusResPackage) {
             mLookupUri = lookupUri;
             mUri = uri;
             mDirectoryId = directoryId;
@@ -159,6 +162,7 @@
             mPhotoId = photoId;
             mPhotoUri = photoUri;
             mDisplayName = displayName;
+            mAltDisplayName = altDisplayName;
             mPhoneticName = phoneticName;
             mStarred = starred;
             mPresence = presence;
@@ -179,6 +183,7 @@
             mPhotoId = from.mPhotoId;
             mPhotoUri = from.mPhotoUri;
             mDisplayName = from.mDisplayName;
+            mAltDisplayName = from.mAltDisplayName;
             mPhoneticName = from.mPhoneticName;
             mStarred = from.mStarred;
             mPresence = from.mPresence;
@@ -257,6 +262,10 @@
             return mDisplayName;
         }
 
+        public String getAltDisplayName() {
+            return mAltDisplayName;
+        }
+
         public String getPhoneticName() {
             return mPhoneticName;
         }
@@ -380,6 +389,7 @@
                 Contacts.DISPLAY_NAME_SOURCE,
                 Contacts.LOOKUP_KEY,
                 Contacts.DISPLAY_NAME,
+                Contacts.DISPLAY_NAME_ALTERNATIVE,
                 Contacts.PHONETIC_NAME,
                 Contacts.PHOTO_ID,
                 Contacts.STARRED,
@@ -447,67 +457,68 @@
         public final static int DISPLAY_NAME_SOURCE = 1;
         public final static int LOOKUP_KEY = 2;
         public final static int DISPLAY_NAME = 3;
-        public final static int PHONETIC_NAME = 4;
-        public final static int PHOTO_ID = 5;
-        public final static int STARRED = 6;
-        public final static int CONTACT_PRESENCE = 7;
-        public final static int CONTACT_STATUS = 8;
-        public final static int CONTACT_STATUS_TIMESTAMP = 9;
-        public final static int CONTACT_STATUS_RES_PACKAGE = 10;
-        public final static int CONTACT_STATUS_LABEL = 11;
-        public final static int CONTACT_ID = 12;
-        public final static int RAW_CONTACT_ID = 13;
+        public final static int ALT_DISPLAY_NAME = 4;
+        public final static int PHONETIC_NAME = 5;
+        public final static int PHOTO_ID = 6;
+        public final static int STARRED = 7;
+        public final static int CONTACT_PRESENCE = 8;
+        public final static int CONTACT_STATUS = 9;
+        public final static int CONTACT_STATUS_TIMESTAMP = 10;
+        public final static int CONTACT_STATUS_RES_PACKAGE = 11;
+        public final static int CONTACT_STATUS_LABEL = 12;
+        public final static int CONTACT_ID = 13;
+        public final static int RAW_CONTACT_ID = 14;
 
-        public final static int ACCOUNT_NAME = 14;
-        public final static int ACCOUNT_TYPE = 15;
-        public final static int DIRTY = 16;
-        public final static int VERSION = 17;
-        public final static int SOURCE_ID = 18;
-        public final static int SYNC1 = 19;
-        public final static int SYNC2 = 20;
-        public final static int SYNC3 = 21;
-        public final static int SYNC4 = 22;
-        public final static int DELETED = 23;
-        public final static int IS_RESTRICTED = 24;
-        public final static int NAME_VERIFIED = 25;
+        public final static int ACCOUNT_NAME = 15;
+        public final static int ACCOUNT_TYPE = 16;
+        public final static int DIRTY = 17;
+        public final static int VERSION = 18;
+        public final static int SOURCE_ID = 19;
+        public final static int SYNC1 = 20;
+        public final static int SYNC2 = 21;
+        public final static int SYNC3 = 22;
+        public final static int SYNC4 = 23;
+        public final static int DELETED = 24;
+        public final static int IS_RESTRICTED = 25;
+        public final static int NAME_VERIFIED = 26;
 
-        public final static int DATA_ID = 26;
-        public final static int DATA1 = 27;
-        public final static int DATA2 = 28;
-        public final static int DATA3 = 29;
-        public final static int DATA4 = 30;
-        public final static int DATA5 = 31;
-        public final static int DATA6 = 32;
-        public final static int DATA7 = 33;
-        public final static int DATA8 = 34;
-        public final static int DATA9 = 35;
-        public final static int DATA10 = 36;
-        public final static int DATA11 = 37;
-        public final static int DATA12 = 38;
-        public final static int DATA13 = 39;
-        public final static int DATA14 = 40;
-        public final static int DATA15 = 41;
-        public final static int DATA_SYNC1 = 42;
-        public final static int DATA_SYNC2 = 43;
-        public final static int DATA_SYNC3 = 44;
-        public final static int DATA_SYNC4 = 45;
-        public final static int DATA_VERSION = 46;
-        public final static int IS_PRIMARY = 47;
-        public final static int IS_SUPERPRIMARY = 48;
-        public final static int MIMETYPE = 49;
-        public final static int RES_PACKAGE = 50;
+        public final static int DATA_ID = 27;
+        public final static int DATA1 = 28;
+        public final static int DATA2 = 29;
+        public final static int DATA3 = 30;
+        public final static int DATA4 = 31;
+        public final static int DATA5 = 32;
+        public final static int DATA6 = 33;
+        public final static int DATA7 = 34;
+        public final static int DATA8 = 35;
+        public final static int DATA9 = 36;
+        public final static int DATA10 = 37;
+        public final static int DATA11 = 38;
+        public final static int DATA12 = 39;
+        public final static int DATA13 = 40;
+        public final static int DATA14 = 41;
+        public final static int DATA15 = 42;
+        public final static int DATA_SYNC1 = 43;
+        public final static int DATA_SYNC2 = 44;
+        public final static int DATA_SYNC3 = 45;
+        public final static int DATA_SYNC4 = 46;
+        public final static int DATA_VERSION = 47;
+        public final static int IS_PRIMARY = 48;
+        public final static int IS_SUPERPRIMARY = 49;
+        public final static int MIMETYPE = 50;
+        public final static int RES_PACKAGE = 51;
 
-        public final static int GROUP_SOURCE_ID = 51;
+        public final static int GROUP_SOURCE_ID = 52;
 
-        public final static int PRESENCE = 52;
-        public final static int CHAT_CAPABILITY = 53;
-        public final static int STATUS = 54;
-        public final static int STATUS_RES_PACKAGE = 55;
-        public final static int STATUS_ICON = 56;
-        public final static int STATUS_LABEL = 57;
-        public final static int STATUS_TIMESTAMP = 58;
+        public final static int PRESENCE = 53;
+        public final static int CHAT_CAPABILITY = 54;
+        public final static int STATUS = 55;
+        public final static int STATUS_RES_PACKAGE = 56;
+        public final static int STATUS_ICON = 57;
+        public final static int STATUS_LABEL = 58;
+        public final static int STATUS_TIMESTAMP = 59;
 
-        public final static int PHOTO_URI = 59;
+        public final static int PHOTO_URI = 60;
     }
 
     private static class DirectoryQuery {
@@ -699,6 +710,7 @@
             final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID);
             final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE);
             final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME);
+            final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME);
             final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME);
             final long photoId = cursor.getLong(ContactQuery.PHOTO_ID);
             final String photoUri = cursor.getString(ContactQuery.PHOTO_URI);
@@ -726,8 +738,8 @@
 
             return new Result(contactUri, lookupUri, directoryId, lookupKey, contactId,
                     nameRawContactId, displayNameSource, photoId, photoUri, displayName,
-                    phoneticName, starred, presence, status, statusTimestamp, statusLabel,
-                    statusResPackage);
+                    altDisplayName, phoneticName, starred, presence, status, statusTimestamp,
+                    statusLabel, statusResPackage);
         }
 
         /**
diff --git a/src/com/android/contacts/detail/ContactDetailHeaderView.java b/src/com/android/contacts/detail/ContactDetailHeaderView.java
index 4b211b6..b88b43e 100644
--- a/src/com/android/contacts/detail/ContactDetailHeaderView.java
+++ b/src/com/android/contacts/detail/ContactDetailHeaderView.java
@@ -20,6 +20,8 @@
 import com.android.contacts.ContactLoader.Result;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
+import com.android.contacts.format.FormatUtils;
+import com.android.contacts.preference.ContactsPreferences;
 import com.android.contacts.util.ContactBadgeUtil;
 
 import android.content.ClipData;
@@ -31,10 +33,13 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Typeface;
 import android.net.Uri;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.DisplayNameSources;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -122,7 +127,8 @@
     public void loadData(ContactLoader.Result contactData) {
         mContactUri = contactData.getLookupUri();
 
-        setDisplayName(contactData.getDisplayName(), contactData.getPhoneticName());
+        setDisplayName(contactData.getDisplayName(), contactData.getAltDisplayName(),
+                contactData.getPhoneticName());
         setCompany(contactData);
         if (contactData.isLoadingPhoto()) {
             setPhoto(null, false);
@@ -188,8 +194,36 @@
     /**
      * Set the display name and phonetic name to show in the header.
      */
-    private void setDisplayName(CharSequence displayName, CharSequence phoneticName) {
-        mDisplayNameView.setText(displayName);
+    private void setDisplayName(CharSequence displayName, CharSequence altDisplayName,
+            CharSequence phoneticName) {
+
+        // Check the preference for display name ordering, and bold the contact's first name if
+        // possible.
+        ContactsPreferences prefs = new ContactsPreferences(getContext());
+        CharSequence styledName;
+        if (prefs.getDisplayOrder() == ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY) {
+            int overlapPoint = FormatUtils.overlapPoint(
+                    displayName.toString(), altDisplayName.toString());
+            if (overlapPoint > 0) {
+                styledName = FormatUtils.applyStyleToSpan(Typeface.BOLD,
+                        displayName, 0, overlapPoint, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {
+                styledName = displayName;
+            }
+        } else {
+            // Displaying alternate display name.
+            int overlapPoint = FormatUtils.overlapPoint(
+                    altDisplayName.toString(), displayName.toString());
+            if (overlapPoint > 0) {
+                styledName = FormatUtils.applyStyleToSpan(Typeface.BOLD,
+                        altDisplayName, overlapPoint, altDisplayName.length(),
+                        Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            } else {
+                styledName = altDisplayName;
+            }
+        }
+        mDisplayNameView.setText(styledName);
+
         if (TextUtils.isEmpty(phoneticName)) {
             mPhoneticNameView.setVisibility(View.GONE);
         } else {
diff --git a/src/com/android/contacts/editor/StructuredNameEditorView.java b/src/com/android/contacts/editor/StructuredNameEditorView.java
index 6daf7b4..5a2ffd3 100644
--- a/src/com/android/contacts/editor/StructuredNameEditorView.java
+++ b/src/com/android/contacts/editor/StructuredNameEditorView.java
@@ -19,19 +19,20 @@
 import com.android.contacts.model.DataKind;
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.util.NameConverter;
 
 import android.content.ContentValues;
 import android.content.Context;
-import android.database.Cursor;
 import android.net.Uri;
-import android.net.Uri.Builder;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * A dedicated editor for structured name.  When the user collapses/expands
  * the structured name, it will reparse or recompose the name, but only
@@ -107,29 +108,20 @@
         ValuesDelta values = getValues();
 
         if (!mChanged) {
-            values.put(StructuredName.PREFIX,
-                    mSnapshot.getAsString(StructuredName.PREFIX));
-            values.put(StructuredName.GIVEN_NAME,
-                    mSnapshot.getAsString(StructuredName.GIVEN_NAME));
-            values.put(StructuredName.MIDDLE_NAME,
-                    mSnapshot.getAsString(StructuredName.MIDDLE_NAME));
-            values.put(StructuredName.FAMILY_NAME,
-                    mSnapshot.getAsString(StructuredName.FAMILY_NAME));
-            values.put(StructuredName.SUFFIX,
-                    mSnapshot.getAsString(StructuredName.SUFFIX));
+            for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
+                values.put(field, mSnapshot.getAsString(field));
+            }
             return;
         }
 
         String displayName = values.getAsString(StructuredName.DISPLAY_NAME);
-        ContentValues tmpCVs = buildStructuredNameFromFullName(
-                getContext(), displayName, null);
-        if (tmpCVs.size() > 0) {
+        Map<String, String> structuredNameMap = NameConverter.displayNameToStructuredName(
+                getContext(), displayName);
+        if (!structuredNameMap.isEmpty()) {
             eraseFullName(values);
-            values.put(StructuredName.PREFIX, tmpCVs.getAsString(StructuredName.PREFIX));
-            values.put(StructuredName.GIVEN_NAME, tmpCVs.getAsString(StructuredName.GIVEN_NAME));
-            values.put(StructuredName.MIDDLE_NAME, tmpCVs.getAsString(StructuredName.MIDDLE_NAME));
-            values.put(StructuredName.FAMILY_NAME, tmpCVs.getAsString(StructuredName.FAMILY_NAME));
-            values.put(StructuredName.SUFFIX, tmpCVs.getAsString(StructuredName.SUFFIX));
+            for (String field : structuredNameMap.keySet()) {
+                values.put(field, structuredNameMap.get(field));
+            }
         }
 
         mSnapshot.clear();
@@ -137,37 +129,6 @@
         mSnapshot.put(StructuredName.DISPLAY_NAME, displayName);
     }
 
-    public static ContentValues buildStructuredNameFromFullName(
-            Context context, String displayName, ContentValues contentValues) {
-        if (contentValues == null) {
-            contentValues = new ContentValues();
-        }
-
-        Builder builder = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name");
-        appendQueryParameter(builder, StructuredName.DISPLAY_NAME, displayName);
-        Cursor cursor = context.getContentResolver().query(builder.build(), new String[]{
-                StructuredName.PREFIX,
-                StructuredName.GIVEN_NAME,
-                StructuredName.MIDDLE_NAME,
-                StructuredName.FAMILY_NAME,
-                StructuredName.SUFFIX,
-        }, null, null, null);
-
-        try {
-            if (cursor.moveToFirst()) {
-                contentValues.put(StructuredName.PREFIX, cursor.getString(0));
-                contentValues.put(StructuredName.GIVEN_NAME, cursor.getString(1));
-                contentValues.put(StructuredName.MIDDLE_NAME, cursor.getString(2));
-                contentValues.put(StructuredName.FAMILY_NAME, cursor.getString(3));
-                contentValues.put(StructuredName.SUFFIX, cursor.getString(4));
-            }
-        } finally {
-            cursor.close();
-        }
-
-        return contentValues;
-    }
-
     private void switchFromStructuredNameToFullName() {
         ValuesDelta values = getValues();
 
@@ -177,14 +138,9 @@
             return;
         }
 
-        String prefix = values.getAsString(StructuredName.PREFIX);
-        String givenName = values.getAsString(StructuredName.GIVEN_NAME);
-        String middleName = values.getAsString(StructuredName.MIDDLE_NAME);
-        String familyName = values.getAsString(StructuredName.FAMILY_NAME);
-        String suffix = values.getAsString(StructuredName.SUFFIX);
-
-        String displayName = buildFullNameFromStructuredName(getContext(),
-                prefix, givenName, middleName, familyName, suffix);
+        Map<String, String> structuredNameMap = valuesToStructuredNameMap(values);
+        String displayName = NameConverter.structuredNameToDisplayName(getContext(),
+                structuredNameMap);
         if (!TextUtils.isEmpty(displayName)) {
             eraseStructuredName(values);
             values.put(StructuredName.DISPLAY_NAME, displayName);
@@ -192,35 +148,17 @@
 
         mSnapshot.clear();
         mSnapshot.put(StructuredName.DISPLAY_NAME, values.getAsString(StructuredName.DISPLAY_NAME));
-        mSnapshot.put(StructuredName.PREFIX, prefix);
-        mSnapshot.put(StructuredName.GIVEN_NAME, givenName);
-        mSnapshot.put(StructuredName.MIDDLE_NAME, middleName);
-        mSnapshot.put(StructuredName.FAMILY_NAME, familyName);
-        mSnapshot.put(StructuredName.SUFFIX, suffix);
+        for (String field : structuredNameMap.keySet()) {
+            mSnapshot.put(field, structuredNameMap.get(field));
+        }
     }
 
-    public static String buildFullNameFromStructuredName(Context context,
-            String prefix, String given, String middle, String family, String suffix) {
-        Uri.Builder builder = ContactsContract.AUTHORITY_URI.buildUpon()
-                .appendPath("complete_name");
-        appendQueryParameter(builder, StructuredName.PREFIX, prefix);
-        appendQueryParameter(builder, StructuredName.GIVEN_NAME, given);
-        appendQueryParameter(builder, StructuredName.MIDDLE_NAME, middle);
-        appendQueryParameter(builder, StructuredName.FAMILY_NAME, family);
-        appendQueryParameter(builder, StructuredName.SUFFIX, suffix);
-        Cursor cursor = context.getContentResolver().query(builder.build(), new String[]{
-                StructuredName.DISPLAY_NAME,
-        }, null, null, null);
-
-        try {
-            if (cursor.moveToFirst()) {
-                return cursor.getString(0);
-            }
-        } finally {
-            cursor.close();
+    private Map<String, String> valuesToStructuredNameMap(ValuesDelta values) {
+        Map<String, String> structuredNameMap = new HashMap<String, String>();
+        for (String key : NameConverter.STRUCTURED_NAME_FIELDS) {
+            structuredNameMap.put(key, values.getAsString(key));
         }
-
-        return null;
+        return structuredNameMap;
     }
 
     private void eraseFullName(ValuesDelta values) {
@@ -228,11 +166,9 @@
     }
 
     private void eraseStructuredName(ValuesDelta values) {
-        values.putNull(StructuredName.PREFIX);
-        values.putNull(StructuredName.GIVEN_NAME);
-        values.putNull(StructuredName.MIDDLE_NAME);
-        values.putNull(StructuredName.FAMILY_NAME);
-        values.putNull(StructuredName.SUFFIX);
+        for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
+            values.putNull(field);
+        }
     }
 
     private static void appendQueryParameter(Uri.Builder builder, String field, String value) {
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index e74492c..04a9485 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -22,19 +22,22 @@
 import com.android.contacts.model.DataKind;
 import com.android.contacts.model.EntityDelta;
 import com.android.contacts.model.EntityDelta.ValuesDelta;
+import com.android.contacts.util.NameConverter;
 
 import android.content.Context;
 import android.content.Entity;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.telephony.PhoneNumberFormattingTextWatcher;
 import android.text.Editable;
 import android.text.InputType;
+import android.text.Spannable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
+import android.text.style.StyleSpan;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
@@ -42,7 +45,8 @@
 import android.widget.EditText;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
-import android.widget.Toast;
+
+import java.util.Map;
 
 /**
  * Simple editor that handles labels and any {@link EditField} defined for the
@@ -189,6 +193,9 @@
             }
             int inputType = field.inputType;
             fieldView.setInputType(inputType);
+            if (field.isFullName) {
+                fieldView.addTextChangedListener(new NameFormattingTextWatcher());
+            }
             if (inputType == InputType.TYPE_CLASS_PHONE) {
                 fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher(
                         ContactsUtils.getCurrentCountryIso(mContext)));
@@ -351,4 +358,60 @@
             }
         };
     }
+
+    private class NameFormattingTextWatcher implements TextWatcher {
+
+
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+        @Override
+        public void afterTextChanged(Editable s) {
+            String displayName = s.toString();
+            Map<String, String> structuredName = NameConverter.displayNameToStructuredName(
+                    getContext(), displayName);
+            String givenName = structuredName.get(StructuredName.GIVEN_NAME);
+            if (!TextUtils.isEmpty(givenName)) {
+                int spanStart = -1;
+                int spanEnd = -1;
+                if (displayName.startsWith(givenName + " ")) {
+                    spanStart = 0;
+                    spanEnd = givenName.length();
+                } else {
+                    spanStart = displayName.lastIndexOf(" " + givenName);
+                    if (spanStart > -1) {
+                        spanStart++;
+                        spanEnd = spanStart + givenName.length();
+                    }
+                }
+
+                // If the requested range is already bolded, don't make any changes.
+                if (spanStart > -1) {
+                    StyleSpan[] existingSpans = s.getSpans(0, s.length(), StyleSpan.class);
+                    for (StyleSpan span : existingSpans) {
+                        if (span.getStyle() == Typeface.BOLD
+                                && s.getSpanStart(span.getUnderlying()) == spanStart
+                                && s.getSpanEnd(span.getUnderlying()) == spanEnd) {
+                            // Nothing to do - the correct portion is already bolded.
+                            return;
+                        }
+                    }
+
+                    // Clear any existing bold style spans.
+                    for (StyleSpan span : existingSpans) {
+                        if (span.getStyle() == Typeface.BOLD) {
+                            s.removeSpan(span);
+                        }
+                    }
+
+                    // Set the new bold span.
+                    s.setSpan(new StyleSpan(Typeface.BOLD), spanStart, spanEnd,
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/contacts/format/FormatUtils.java b/src/com/android/contacts/format/FormatUtils.java
new file mode 100644
index 0000000..ac34d6d
--- /dev/null
+++ b/src/com/android/contacts/format/FormatUtils.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2011 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.contacts.format;
+
+import android.database.CharArrayBuffer;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.style.StyleSpan;
+
+import java.util.Arrays;
+
+/**
+ * Assorted utility methods related to text formatting in Contacts.
+ */
+public class FormatUtils {
+
+    /**
+     * Finds the earliest point in buffer1 at which the first part of buffer2 matches.  For example,
+     * overlapPoint("abcd", "cdef") == 2.
+     */
+    public static int overlapPoint(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
+        if (buffer1 == null || buffer2 == null) {
+            return -1;
+        }
+        return overlapPoint(Arrays.copyOfRange(buffer1.data, 0, buffer1.sizeCopied),
+                Arrays.copyOfRange(buffer2.data, 0, buffer2.sizeCopied));
+    }
+
+    /**
+     * Finds the earliest point in string1 at which the first part of string2 matches.  For example,
+     * overlapPoint("abcd", "cdef") == 2.
+     */
+    public static int overlapPoint(String string1, String string2) {
+        if (string1 == null || string2 == null) {
+            return -1;
+        }
+        return overlapPoint(string1.toCharArray(), string2.toCharArray());
+    }
+
+    /**
+     * Finds the earliest point in array1 at which the first part of array2 matches.  For example,
+     * overlapPoint("abcd", "cdef") == 2.
+     */
+    public static int overlapPoint(char[] array1, char[] array2) {
+        if (array1 == null || array2 == null) {
+            return -1;
+        }
+        int count1 = array1.length;
+        int count2 = array2.length;
+
+        // Ignore matching tails of the two arrays.
+        while (count1 > 0 && count2 > 0 && array1[count1 - 1] == array2[count2 - 1]) {
+            count1--;
+            count2--;
+        }
+
+        int size = count2;
+        for (int i = 0; i < count1; i++) {
+            if (i + size > count1) {
+                size = count1 - i;
+            }
+            int j;
+            for (j = 0; j < size; j++) {
+                if (array1[i+j] != array2[j]) {
+                    break;
+                }
+            }
+            if (j == size) {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+    /**
+     * Applies the given style to a range of the input CharSequence.
+     * @param style The style to apply (see the style constants in {@link Typeface}).
+     * @param input The CharSequence to style.
+     * @param start Starting index of the range to style (will be clamped to be a minimum of 0).
+     * @param end Ending index of the range to style (will be clamped to a maximum of the input
+     *     length).
+     * @param flags Bitmask for configuring behavior of the span.  See {@link android.text.Spanned}.
+     * @return The styled CharSequence.
+     */
+    public static CharSequence applyStyleToSpan(int style, CharSequence input, int start, int end,
+            int flags) {
+        // Enforce bounds of the char sequence.
+        start = Math.max(0, start);
+        end = Math.min(input.length(), end);
+        SpannableString text = new SpannableString(input);
+        text.setSpan(new StyleSpan(style), start, end, flags);
+        return text;
+    }
+}
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index cfacc85..2c57983 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -274,8 +274,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
-                mAlternativeDisplayNameColumnIndex);
+        view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
+                isNameHighlightingEnabled(), getContactNameDisplayOrder());
         view.showPhoneticName(cursor, CONTACT_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index fe5f8e2..022c2f6 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.ContactPresenceIconUtil;
 import com.android.contacts.R;
+import com.android.contacts.format.FormatUtils;
 import com.android.contacts.widget.TextWithHighlighting;
 import com.android.contacts.widget.TextWithHighlightingFactory;
 
@@ -30,8 +31,11 @@
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.Contacts;
+import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.style.ForegroundColorSpan;
@@ -45,6 +49,8 @@
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
 
+import java.util.Arrays;
+
 /**
  * A custom view for an item in the contact list.
  */
@@ -805,9 +811,21 @@
         return mActivatedStateSupported ? TruncateAt.START : TruncateAt.MARQUEE;
     }
 
-    public void showDisplayName(Cursor cursor, int nameColumnIndex, boolean highlightingEnabled,
-            int alternativeNameColumnIndex) {
+    public void showDisplayName(Cursor cursor, int nameColumnIndex, int alternativeNameColumnIndex,
+            boolean highlightingEnabled, int displayOrder) {
+
+        // Copy out the display name and alternate display name, and compute the point at which
+        // the two overlap (for bolding).
         cursor.copyStringToBuffer(nameColumnIndex, mNameBuffer);
+        cursor.copyStringToBuffer(alternativeNameColumnIndex, mHighlightedTextBuffer);
+        int overlapPoint = FormatUtils.overlapPoint(mNameBuffer, mHighlightedTextBuffer);
+        int boldStart = 0;
+        int boldEnd = overlapPoint;
+        if (displayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
+            boldStart = overlapPoint;
+            boldEnd = mNameBuffer.sizeCopied;
+        }
+
         TextView nameView = getNameTextView();
         int size = mNameBuffer.sizeCopied;
         if (size != 0) {
@@ -818,11 +836,24 @@
                     mTextWithHighlighting =
                             mTextWithHighlightingFactory.createTextWithHighlighting();
                 }
-                cursor.copyStringToBuffer(alternativeNameColumnIndex, mHighlightedTextBuffer);
                 mTextWithHighlighting.setText(mNameBuffer, mHighlightedTextBuffer);
-                nameView.setText(mTextWithHighlighting);
+                if (overlapPoint > 0) {
+                    // Bold the first name.
+                    nameView.setText(FormatUtils.applyStyleToSpan(Typeface.BOLD,
+                            mTextWithHighlighting, boldStart, boldEnd,
+                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE));
+                } else {
+                    nameView.setText(mTextWithHighlighting);
+                }
             } else {
-                nameView.setText(mNameBuffer.data, 0, size);
+                if (overlapPoint > 0) {
+                    // Bold the first name.
+                    nameView.setText(FormatUtils.applyStyleToSpan(Typeface.BOLD,
+                            new String(Arrays.copyOfRange(mNameBuffer.data, 0, size)),
+                            boldStart, boldEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE));
+                } else {
+                    nameView.setText(mNameBuffer.data, 0, size);
+                }
             }
         } else {
             nameView.setText(mUnknownNameText);
@@ -934,7 +965,9 @@
             }
 
             String string = new String(text.data, 0, text.sizeCopied);
-            SpannableString name = new SpannableString(string);
+            SpannableString name = new SpannableString(
+                    FormatUtils.applyStyleToSpan(Typeface.BOLD, string, 0, index,
+                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE));
             name.setSpan(mPrefixColorSpan, index, index + mHighlightedPrefix.length, 0 /* flags */);
             textView.setText(name);
         } else {
diff --git a/src/com/android/contacts/list/EmailAddressListAdapter.java b/src/com/android/contacts/list/EmailAddressListAdapter.java
index 96c69f9..eae02b5 100644
--- a/src/com/android/contacts/list/EmailAddressListAdapter.java
+++ b/src/com/android/contacts/list/EmailAddressListAdapter.java
@@ -168,8 +168,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
-                mAlternativeDisplayNameColumnIndex);
+        view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
+                isNameHighlightingEnabled(), getContactNameDisplayOrder());
 //        view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/LegacyContactListAdapter.java b/src/com/android/contacts/list/LegacyContactListAdapter.java
index 6747d1f..ffc8fc3 100644
--- a/src/com/android/contacts/list/LegacyContactListAdapter.java
+++ b/src/com/android/contacts/list/LegacyContactListAdapter.java
@@ -85,7 +85,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, PERSON_DISPLAY_NAME_COLUMN_INDEX, false, 0);
+        view.showDisplayName(cursor, PERSON_DISPLAY_NAME_COLUMN_INDEX, 0, false,
+                getContactNameDisplayOrder());
         view.showPhoneticName(cursor, PERSON_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
index 47747fb..547650d 100644
--- a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
@@ -89,7 +89,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, PHONE_DISPLAY_NAME_COLUMN_INDEX, false, 0);
+        view.showDisplayName(cursor, PHONE_DISPLAY_NAME_COLUMN_INDEX, 0, false,
+                getContactNameDisplayOrder());
         view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
index 3796c62..48b3f0c 100644
--- a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
@@ -90,7 +90,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, POSTAL_DISPLAY_NAME_COLUMN_INDEX, false, 0);
+        view.showDisplayName(cursor, POSTAL_DISPLAY_NAME_COLUMN_INDEX, 0, false,
+                getContactNameDisplayOrder());
         view.showPhoneticName(cursor, POSTAL_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 48a6042..9356bb6 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -185,8 +185,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
-                mAlternativeDisplayNameColumnIndex);
+        view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
+                isNameHighlightingEnabled(), getContactNameDisplayOrder());
         view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java
index 8b3c75d..86d465a 100644
--- a/src/com/android/contacts/list/PostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/PostalAddressListAdapter.java
@@ -156,8 +156,8 @@
     }
 
     protected void bindName(final ContactListItemView view, Cursor cursor) {
-        view.showDisplayName(cursor, mDisplayNameColumnIndex, isNameHighlightingEnabled(),
-                mAlternativeDisplayNameColumnIndex);
+        view.showDisplayName(cursor, mDisplayNameColumnIndex, mAlternativeDisplayNameColumnIndex,
+                isNameHighlightingEnabled(), getContactNameDisplayOrder());
 //        view.showPhoneticName(cursor, PHONE_PHONETIC_NAME_COLUMN_INDEX);
     }
 
diff --git a/src/com/android/contacts/model/AccountType.java b/src/com/android/contacts/model/AccountType.java
index 3cce25d..462f7ad 100644
--- a/src/com/android/contacts/model/AccountType.java
+++ b/src/com/android/contacts/model/AccountType.java
@@ -249,6 +249,7 @@
         public boolean optional;
         public boolean shortForm;
         public boolean longForm;
+        public boolean isFullName;
 
         public EditField(String column, int titleRes) {
             this.column = column;
@@ -279,6 +280,11 @@
             this.minLines = minLines;
             return this;
         }
+
+        public EditField setIsFullName(boolean isFullName) {
+            this.isFullName = isFullName;
+            return this;
+        }
     }
 
     /**
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index ee08522..fd8f914 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -136,7 +136,7 @@
 
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(StructuredName.DISPLAY_NAME,
-                R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true));
+                R.string.full_name, FLAGS_PERSON_NAME).setShortForm(true).setIsFullName(true));
 
         boolean displayOrderPrimary =
                 context.getResources().getBoolean(R.bool.config_editor_field_order_primary);
diff --git a/src/com/android/contacts/model/EntityModifier.java b/src/com/android/contacts/model/EntityModifier.java
index 25afe4d..9d527df 100644
--- a/src/com/android/contacts/model/EntityModifier.java
+++ b/src/com/android/contacts/model/EntityModifier.java
@@ -16,12 +16,10 @@
 
 package com.android.contacts.model;
 
-import com.google.android.collect.Lists;
-
 import com.android.contacts.ContactsUtils;
 import com.android.contacts.editor.EventFieldEditorView;
+import com.android.contacts.util.NameConverter;
 import com.android.contacts.editor.PhoneticNameEditorView;
-import com.android.contacts.editor.StructuredNameEditorView;
 import com.android.contacts.model.AccountType.EditField;
 import com.android.contacts.model.AccountType.EditType;
 import com.android.contacts.model.AccountType.EventEditType;
@@ -156,7 +154,7 @@
      */
     private static ArrayList<EditType> getValidTypes(EntityDelta state, DataKind kind,
             EditType forceInclude, boolean includeSecondary, SparseIntArray typeCount) {
-        final ArrayList<EditType> validTypes = Lists.newArrayList();
+        final ArrayList<EditType> validTypes = new ArrayList<EditType>();
 
         // Bail early if no types provided
         if (!hasEditTypes(kind)) return validTypes;
@@ -1038,8 +1036,7 @@
         if (!TextUtils.isEmpty(displayName)) {
             if (!supportDisplayName) {
                 // Old data has a display name, while the new account doesn't allow it.
-                StructuredNameEditorView.buildStructuredNameFromFullName(
-                        context, displayName, values);
+                NameConverter.displayNameToStructuredName(context, displayName, values);
 
                 // We don't want to migrate unseen data which may confuse users after the creation.
                 values.remove(StructuredName.DISPLAY_NAME);
@@ -1048,18 +1045,10 @@
             if (supportDisplayName) {
                 // Old data does not have display name, while the new account requires it.
                 values.put(StructuredName.DISPLAY_NAME,
-                        StructuredNameEditorView.buildFullNameFromStructuredName(context,
-                                values.getAsString(StructuredName.PREFIX),
-                                values.getAsString(StructuredName.GIVEN_NAME),
-                                values.getAsString(StructuredName.MIDDLE_NAME),
-                                values.getAsString(StructuredName.FAMILY_NAME),
-                                values.getAsString(StructuredName.SUFFIX)));
-
-                values.remove(StructuredName.PREFIX);
-                values.remove(StructuredName.GIVEN_NAME);
-                values.remove(StructuredName.MIDDLE_NAME);
-                values.remove(StructuredName.FAMILY_NAME);
-                values.remove(StructuredName.SUFFIX);
+                        NameConverter.structuredNameToDisplayName(context, values));
+                for (String field : NameConverter.STRUCTURED_NAME_FIELDS) {
+                    values.remove(field);
+                }
             }
         }
 
diff --git a/src/com/android/contacts/util/NameConverter.java b/src/com/android/contacts/util/NameConverter.java
new file mode 100644
index 0000000..9853821
--- /dev/null
+++ b/src/com/android/contacts/util/NameConverter.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2011 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.contacts.util;
+
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.net.Uri.Builder;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.text.TextUtils;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Utility class for converting between a display name and structured name (and vice-versa), via
+ * calls to the contact provider.
+ */
+public class NameConverter {
+
+    /**
+     * The array of fields that comprise a structured name.
+     */
+    public static final String[] STRUCTURED_NAME_FIELDS = new String[] {
+            StructuredName.PREFIX,
+            StructuredName.GIVEN_NAME,
+            StructuredName.MIDDLE_NAME,
+            StructuredName.FAMILY_NAME,
+            StructuredName.SUFFIX
+    };
+
+    /**
+     * Converts the given structured name (provided as a map from {@link StructuredName} fields to
+     * corresponding values) into a display name string.
+     * <p>
+     * Note that this operates via a call back to the ContactProvider, but it does not access the
+     * database, so it should be safe to call from the UI thread.  See
+     * ContactsProvider2.completeName() for the underlying method call.
+     * @param context Activity context.
+     * @param structuredName The structured name map to convert.
+     * @return The display name computed from the structured name map.
+     */
+    public static String structuredNameToDisplayName(Context context,
+            Map<String, String> structuredName) {
+        Builder builder = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name");
+        for (String key : STRUCTURED_NAME_FIELDS) {
+            if (structuredName.containsKey(key)) {
+                appendQueryParameter(builder, key, structuredName.get(key));
+            }
+        }
+        return fetchDisplayName(context, builder.build());
+    }
+
+    /**
+     * Converts the given structured name (provided as ContentValues) into a display name string.
+     * @param context Activity context.
+     * @param values The content values containing values comprising the structured name.
+     * @return
+     */
+    public static String structuredNameToDisplayName(Context context, ContentValues values) {
+        Builder builder = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name");
+        for (String key : STRUCTURED_NAME_FIELDS) {
+            if (values.containsKey(key)) {
+                appendQueryParameter(builder, key, values.getAsString(key));
+            }
+        }
+        return fetchDisplayName(context, builder.build());
+    }
+
+    /**
+     * Helper method for fetching the display name via the given URI.
+     */
+    private static String fetchDisplayName(Context context, Uri uri) {
+        String displayName = null;
+        Cursor cursor = context.getContentResolver().query(uri, new String[]{
+                StructuredName.DISPLAY_NAME,
+        }, null, null, null);
+
+        try {
+            if (cursor.moveToFirst()) {
+                displayName = cursor.getString(0);
+            }
+        } finally {
+            cursor.close();
+        }
+        return displayName;
+    }
+
+    /**
+     * Converts the given display name string into a structured name (as a map from
+     * {@link StructuredName} fields to corresponding values).
+     * <p>
+     * Note that this operates via a call back to the ContactProvider, but it does not access the
+     * database, so it should be safe to call from the UI thread.
+     * @param context Activity context.
+     * @param displayName The display name to convert.
+     * @return The structured name map computed from the display name.
+     */
+    public static Map<String, String> displayNameToStructuredName(Context context,
+            String displayName) {
+        Map<String, String> structuredName = new TreeMap<String, String>();
+        Builder builder = ContactsContract.AUTHORITY_URI.buildUpon().appendPath("complete_name");
+
+        appendQueryParameter(builder, StructuredName.DISPLAY_NAME, displayName);
+        Cursor cursor = context.getContentResolver().query(builder.build(), STRUCTURED_NAME_FIELDS,
+                null, null, null);
+
+        try {
+            if (cursor.moveToFirst()) {
+                for (int i = 0; i < STRUCTURED_NAME_FIELDS.length; i++) {
+                    structuredName.put(STRUCTURED_NAME_FIELDS[i], cursor.getString(i));
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+        return structuredName;
+    }
+
+    /**
+     * Converts the given display name string into a structured name (inserting the structured
+     * values into a new or existing ContentValues object).
+     * <p>
+     * Note that this operates via a call back to the ContactProvider, but it does not access the
+     * database, so it should be safe to call from the UI thread.
+     * @param context Activity context.
+     * @param displayName The display name to convert.
+     * @param contentValues The content values object to place the structured name values into.  If
+     *     null, a new one will be created and returned.
+     * @return The ContentValues object containing the structured name fields derived from the
+     *     display name.
+     */
+    public static ContentValues displayNameToStructuredName(Context context, String displayName,
+            ContentValues contentValues) {
+        if (contentValues == null) {
+            contentValues = new ContentValues();
+        }
+        Map<String, String> mapValues = displayNameToStructuredName(context, displayName);
+        for (String key : mapValues.keySet()) {
+            contentValues.put(key, mapValues.get(key));
+        }
+        return contentValues;
+    }
+
+    private static void appendQueryParameter(Builder builder, String field, String value) {
+        if (!TextUtils.isEmpty(value)) {
+            builder.appendQueryParameter(field, value);
+        }
+    }
+}
diff --git a/src/com/android/contacts/widget/TextHighlightingAnimation.java b/src/com/android/contacts/widget/TextHighlightingAnimation.java
index 21bbc63..882dd48 100644
--- a/src/com/android/contacts/widget/TextHighlightingAnimation.java
+++ b/src/com/android/contacts/widget/TextHighlightingAnimation.java
@@ -15,6 +15,7 @@
  */
 package com.android.contacts.widget;
 
+import com.android.contacts.format.FormatUtils;
 import com.android.internal.R;
 
 import android.database.CharArrayBuffer;
@@ -72,7 +73,7 @@
             // TODO figure out a way to avoid string allocation
             mString = new String(mText.data, 0, mText.sizeCopied);
 
-            int index = indexOf(baseText, highlightedText);
+            int index = FormatUtils.overlapPoint(baseText, highlightedText);
 
             if (index == 0 || index == -1) {
                 mDimmingEnabled = false;
@@ -83,42 +84,6 @@
             }
         }
 
-        /**
-         * An implementation of indexOf on CharArrayBuffers that finds the first match of
-         * the start of buffer2 in buffer1.  For example, indexOf("abcd", "cdef") == 2
-         */
-        private int indexOf(CharArrayBuffer buffer1, CharArrayBuffer buffer2) {
-            char[] string1 = buffer1.data;
-            char[] string2 = buffer2.data;
-            int count1 = buffer1.sizeCopied;
-            int count2 = buffer2.sizeCopied;
-
-            // Ignore matching tails of the two buffers
-            while (count1 > 0 && count2 > 0 && string1[count1 - 1] == string2[count2 - 1]) {
-                count1--;
-                count2--;
-            }
-
-            int size = count2;
-            for (int i = 0; i < count1; i++) {
-                if (i + size > count1) {
-                    size = count1 - i;
-                }
-                int j;
-                for (j = 0; j < size; j++) {
-                    if (string1[i+j] != string2[j]) {
-                        break;
-                    }
-                }
-                if (j == size) {
-                    return i;
-                }
-            }
-
-            return -1;
-        }
-
-
         @SuppressWarnings("unchecked")
         public <T> T[] getSpans(int start, int end, Class<T> type) {
             if (mDimmingEnabled) {
diff --git a/tests/src/com/android/contacts/format/FormatUtilsTests.java b/tests/src/com/android/contacts/format/FormatUtilsTests.java
new file mode 100644
index 0000000..0464adb
--- /dev/null
+++ b/tests/src/com/android/contacts/format/FormatUtilsTests.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 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.contacts.format;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Test cases for format utility methods.
+ */
+@SmallTest
+public class FormatUtilsTests extends AndroidTestCase {
+
+    public void testOverlapPoint() throws Exception {
+        assertEquals(2, FormatUtils.overlapPoint("abcde", "cdefg"));
+        assertEquals(-1, FormatUtils.overlapPoint("John Doe", "John Doe"));
+        assertEquals(5, FormatUtils.overlapPoint("John Doe", "Doe, John"));
+        assertEquals(-1, FormatUtils.overlapPoint("Mr. John Doe", "Mr. Doe, John"));
+        assertEquals(13, FormatUtils.overlapPoint("John Herbert Doe", "Doe, John Herbert"));
+    }
+}
diff --git a/tests/src/com/android/contacts/util/NameConverterTests.java b/tests/src/com/android/contacts/util/NameConverterTests.java
new file mode 100644
index 0000000..e1773a7
--- /dev/null
+++ b/tests/src/com/android/contacts/util/NameConverterTests.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 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.contacts.util;
+
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for {@link NameConverter}.
+ */
+@SmallTest
+public class NameConverterTests extends AndroidTestCase {
+
+    public void testStructuredNameToDisplayName() {
+        Map<String, String> structuredName = new HashMap<String, String>();
+        structuredName.put(StructuredName.PREFIX, "Mr.");
+        structuredName.put(StructuredName.GIVEN_NAME, "John");
+        structuredName.put(StructuredName.MIDDLE_NAME, "Quincy");
+        structuredName.put(StructuredName.FAMILY_NAME, "Adams");
+        structuredName.put(StructuredName.SUFFIX, "Esquire");
+
+        assertEquals("Mr. John Quincy Adams, Esquire",
+                NameConverter.structuredNameToDisplayName(mContext, structuredName));
+
+        structuredName.remove(StructuredName.SUFFIX);
+        assertEquals("Mr. John Quincy Adams",
+                NameConverter.structuredNameToDisplayName(mContext, structuredName));
+
+        structuredName.remove(StructuredName.MIDDLE_NAME);
+        assertEquals("Mr. John Adams",
+                NameConverter.structuredNameToDisplayName(mContext, structuredName));
+    }
+
+    public void testDisplayNameToStructuredName() {
+        assertStructuredName("Mr. John Quincy Adams, Esquire",
+                "Mr.", "John", "Quincy", "Adams", "Esquire");
+        assertStructuredName("John Doe", null, "John", null, "Doe", null);
+        assertStructuredName("Ms. Jane Eyre", "Ms.", "Jane", null, "Eyre", null);
+        assertStructuredName("Dr Leo Spaceman, PhD", "Dr", "Leo", null, "Spaceman", "PhD");
+    }
+
+    /**
+     * Helper method to check whether a given display name parses out to the other parameters.
+     * @param displayName Display name to break into a structured name.
+     * @param prefix Expected prefix (null if not expected).
+     * @param givenName Expected given name (null if not expected).
+     * @param middleName Expected middle name (null if not expected).
+     * @param familyName Expected family name (null if not expected).
+     * @param suffix Expected suffix (null if not expected).
+     */
+    private void assertStructuredName(String displayName, String prefix,
+            String givenName, String middleName, String familyName, String suffix) {
+        Map<String, String> structuredName = NameConverter.displayNameToStructuredName(mContext,
+                displayName);
+        checkNameComponent(StructuredName.PREFIX, prefix, structuredName);
+        checkNameComponent(StructuredName.GIVEN_NAME, givenName, structuredName);
+        checkNameComponent(StructuredName.MIDDLE_NAME, middleName, structuredName);
+        checkNameComponent(StructuredName.FAMILY_NAME, familyName, structuredName);
+        checkNameComponent(StructuredName.SUFFIX, suffix, structuredName);
+        assertEquals(0, structuredName.size());
+    }
+
+    /**
+     * Checks that the given field and value are present in the structured name map (or not present
+     * if the given value is null).  If the value is present and matches, the key is removed from
+     * the map - once all components of the name are checked, the map should be empty.
+     * @param field Field to check.
+     * @param value Expected value for the field (null if it is not expected to be populated).
+     * @param structuredName The map of structured field names to values.
+     */
+    private void checkNameComponent(String field, String value,
+            Map<String, String> structuredName) {
+        if (TextUtils.isEmpty(value)) {
+            assertNull(structuredName.get(field));
+        } else {
+            assertEquals(value, structuredName.get(field));
+        }
+        structuredName.remove(field);
+    }
+}