Merge "Moving dependencies of PhoneFavoriteFragment."
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7e69de2..7e28847 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -114,4 +114,25 @@
     <!-- Action string for sending an SMS to a MMS phone number -->
     <string name="sms_mms">Text MMS</string>
 
+    <!-- Title of the confirmation dialog for clearing frequents. [CHAR LIMIT=37] -->
+    <string name="clearFrequentsConfirmation_title">Clear frequently contacted?</string>
+
+    <!-- Confirmation dialog for clearing frequents. [CHAR LIMIT=NONE] -->
+    <string name="clearFrequentsConfirmation">You\'ll clear the frequently contacted list in the
+        People and Phone apps, and force email apps to learn your addressing preferences from
+        scratch.
+    </string>
+
+    <!-- Title of the "Clearing frequently contacted" progress-dialog [CHAR LIMIT=35] -->
+    <string name="clearFrequentsProgress_title">Clearing frequently contacted\u2026</string>
+
+    <!--  Used to display as default status when the contact is available for chat [CHAR LIMIT=19] -->
+    <string name="status_available">Available</string>
+
+    <!--  Used to display as default status when the contact is away or idle for chat [CHAR LIMIT=19] -->
+    <string name="status_away">Away</string>
+
+    <!--  Used to display as default status when the contact is busy or Do not disturb for chat [CHAR LIMIT=19] -->
+    <string name="status_busy">Busy</string>
+
 </resources>
diff --git a/src/com/android/contacts/common/ContactPresenceIconUtil.java b/src/com/android/contacts/common/ContactPresenceIconUtil.java
new file mode 100644
index 0000000..2f4c9ee
--- /dev/null
+++ b/src/com/android/contacts/common/ContactPresenceIconUtil.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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.common;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.ContactsContract.StatusUpdates;
+
+/**
+ * Define the contact present show policy in Contacts
+ */
+public class ContactPresenceIconUtil {
+    /**
+     * Get the presence icon resource according the status.
+     *
+     * @return null means don't show the status icon.
+     */
+    public static Drawable getPresenceIcon (Context context, int status) {
+        // We don't show the offline status in Contacts
+        switch(status) {
+            case StatusUpdates.AVAILABLE:
+            case StatusUpdates.IDLE:
+            case StatusUpdates.AWAY:
+            case StatusUpdates.DO_NOT_DISTURB:
+            case StatusUpdates.INVISIBLE:
+                return context.getResources().getDrawable(
+                        StatusUpdates.getPresenceIconResourceId(status));
+            case StatusUpdates.OFFLINE:
+            // The undefined status is treated as OFFLINE in getPresenceIconResourceId();
+            default:
+                return null;
+        }
+    }
+}
diff --git a/src/com/android/contacts/common/ContactStatusUtil.java b/src/com/android/contacts/common/ContactStatusUtil.java
new file mode 100644
index 0000000..a7d1925
--- /dev/null
+++ b/src/com/android/contacts/common/ContactStatusUtil.java
@@ -0,0 +1,47 @@
+/*
+ * 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.common;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.ContactsContract.StatusUpdates;
+
+/**
+ * Provides static function to get default contact status message.
+ */
+public class ContactStatusUtil {
+
+    private static final String TAG = "ContactStatusUtil";
+
+    public static String getStatusString(Context context, int presence) {
+        Resources resources = context.getResources();
+        switch (presence) {
+            case StatusUpdates.AVAILABLE:
+                return resources.getString(R.string.status_available);
+            case StatusUpdates.IDLE:
+            case StatusUpdates.AWAY:
+                return resources.getString(R.string.status_away);
+            case StatusUpdates.DO_NOT_DISTURB:
+                return resources.getString(R.string.status_busy);
+            case StatusUpdates.OFFLINE:
+            case StatusUpdates.INVISIBLE:
+            default:
+                return null;
+        }
+    }
+
+}
diff --git a/src/com/android/contacts/common/ContactTileLoaderFactory.java b/src/com/android/contacts/common/ContactTileLoaderFactory.java
new file mode 100644
index 0000000..068aed8
--- /dev/null
+++ b/src/com/android/contacts/common/ContactTileLoaderFactory.java
@@ -0,0 +1,91 @@
+/*
+ * 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.common;
+
+import android.content.Context;
+import android.content.CursorLoader;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+
+/**
+ * Used to create {@link CursorLoader}s to load different groups of
+ * {@link com.android.contacts.list.ContactTileView}.
+ */
+public final class ContactTileLoaderFactory {
+
+    public final static int CONTACT_ID = 0;
+    public final static int DISPLAY_NAME = 1;
+    public final static int STARRED = 2;
+    public final static int PHOTO_URI = 3;
+    public final static int LOOKUP_KEY = 4;
+    public final static int CONTACT_PRESENCE = 5;
+    public final static int CONTACT_STATUS = 6;
+
+    // Only used for StrequentPhoneOnlyLoader
+    public final static int PHONE_NUMBER = 5;
+    public final static int PHONE_NUMBER_TYPE = 6;
+    public final static int PHONE_NUMBER_LABEL = 7;
+
+    private static final String[] COLUMNS = new String[] {
+        Contacts._ID, // ..........................................0
+        Contacts.DISPLAY_NAME, // .................................1
+        Contacts.STARRED, // ......................................2
+        Contacts.PHOTO_URI, // ....................................3
+        Contacts.LOOKUP_KEY, // ...................................4
+        Contacts.CONTACT_PRESENCE, // .............................5
+        Contacts.CONTACT_STATUS, // ...............................6
+    };
+
+    /**
+     * Projection used for the {@link Contacts#CONTENT_STREQUENT_URI}
+     * query when {@link ContactsContract#STREQUENT_PHONE_ONLY} flag
+     * is set to true. The main difference is the lack of presence
+     * and status data and the addition of phone number and label.
+     */
+    private static final String[] COLUMNS_PHONE_ONLY = new String[] {
+        Contacts._ID, // ..........................................0
+        Contacts.DISPLAY_NAME, // .................................1
+        Contacts.STARRED, // ......................................2
+        Contacts.PHOTO_URI, // ....................................3
+        Contacts.LOOKUP_KEY, // ...................................4
+        Phone.NUMBER, // ..........................................5
+        Phone.TYPE, // ............................................6
+        Phone.LABEL // ............................................7
+    };
+
+    public static CursorLoader createStrequentLoader(Context context) {
+        return new CursorLoader(context, Contacts.CONTENT_STREQUENT_URI, COLUMNS, null, null, null);
+    }
+
+    public static CursorLoader createStrequentPhoneOnlyLoader(Context context) {
+        Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon()
+                .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build();
+
+        return new CursorLoader(context, uri, COLUMNS_PHONE_ONLY, null, null, null);
+    }
+
+    public static CursorLoader createStarredLoader(Context context) {
+        return new CursorLoader(context, Contacts.CONTENT_URI, COLUMNS,
+                Contacts.STARRED + "=?", new String[]{"1"}, Contacts.DISPLAY_NAME + " ASC");
+    }
+
+    public static CursorLoader createFrequentLoader(Context context) {
+        return new CursorLoader(context, Contacts.CONTENT_FREQUENT_URI, COLUMNS,
+                 Contacts.STARRED + "=?", new String[]{"0"}, null);
+    }
+}
diff --git a/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java b/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java
new file mode 100644
index 0000000..2cfd36e
--- /dev/null
+++ b/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 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.common.dialog;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.content.ContentResolver;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+
+import com.android.contacts.common.R;
+
+/**
+ * Dialog that clears the frequently contacted list after confirming with the user.
+ */
+public class ClearFrequentsDialog extends DialogFragment {
+    /** Preferred way to show this dialog */
+    public static void show(FragmentManager fragmentManager) {
+        ClearFrequentsDialog dialog = new ClearFrequentsDialog();
+        dialog.show(fragmentManager, "clearFrequents");
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        final ContentResolver resolver = getActivity().getContentResolver();
+        final OnClickListener okListener = new OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                final IndeterminateProgressDialog progressDialog = IndeterminateProgressDialog.show(
+                        getFragmentManager(), getString(R.string.clearFrequentsProgress_title),
+                        null, 500);
+                final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        resolver.delete(ContactsContract.DataUsageFeedback.DELETE_USAGE_URI,
+                                null, null);
+                        return null;
+                    }
+
+                    @Override
+                    protected void onPostExecute(Void result) {
+                        progressDialog.dismiss();
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            }
+        };
+        return new AlertDialog.Builder(getActivity())
+            .setTitle(R.string.clearFrequentsConfirmation_title)
+            .setMessage(R.string.clearFrequentsConfirmation)
+            .setNegativeButton(android.R.string.cancel, null)
+            .setPositiveButton(android.R.string.ok, okListener)
+            .setCancelable(true)
+            .create();
+    }
+}
diff --git a/src/com/android/contacts/common/dialog/IndeterminateProgressDialog.java b/src/com/android/contacts/common/dialog/IndeterminateProgressDialog.java
new file mode 100644
index 0000000..2fe059f
--- /dev/null
+++ b/src/com/android/contacts/common/dialog/IndeterminateProgressDialog.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2012 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.common.dialog;
+
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.FragmentManager;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+
+/**
+ * Indeterminate progress dialog wrapped up in a DialogFragment to work even when the device
+ * orientation is changed. Currently, only supports adding a title and/or message to the progress
+ * dialog.  There is an additional parameter of the minimum amount of time to display the progress
+ * dialog even after a call to dismiss the dialog {@link #dismiss()} or
+ * {@link #dismissAllowingStateLoss()}.
+ * <p>
+ * To create and show the progress dialog, use
+ * {@link #show(FragmentManager, CharSequence, CharSequence, long)} and retain the reference to the
+ * IndeterminateProgressDialog instance.
+ * <p>
+ * To dismiss the dialog, use {@link #dismiss()} or {@link #dismissAllowingStateLoss()} on the
+ * instance.  The instance returned by
+ * {@link #show(FragmentManager, CharSequence, CharSequence, long)} is guaranteed to be valid
+ * after a device orientation change because the {@link #setRetainInstance(boolean)} is called
+ * internally with true.
+ */
+public class IndeterminateProgressDialog extends DialogFragment {
+    private static final String TAG = IndeterminateProgressDialog.class.getSimpleName();
+
+    private CharSequence mTitle;
+    private CharSequence mMessage;
+    private long mMinDisplayTime;
+    private long mShowTime = 0;
+    private boolean mActivityReady = false;
+    private Dialog mOldDialog;
+    private final Handler mHandler = new Handler();
+    private boolean mCalledSuperDismiss = false;
+    private boolean mAllowStateLoss;
+    private final Runnable mDismisser = new Runnable() {
+        @Override
+        public void run() {
+            superDismiss();
+        }
+    };
+
+    /**
+     * Creates and shows an indeterminate progress dialog.  Once the progress dialog is shown, it
+     * will be shown for at least the minDisplayTime (in milliseconds), so that the progress dialog
+     * does not flash in and out to quickly.
+     */
+    public static IndeterminateProgressDialog show(FragmentManager fragmentManager,
+            CharSequence title, CharSequence message, long minDisplayTime) {
+        IndeterminateProgressDialog dialogFragment = new IndeterminateProgressDialog();
+        dialogFragment.mTitle = title;
+        dialogFragment.mMessage = message;
+        dialogFragment.mMinDisplayTime = minDisplayTime;
+        dialogFragment.show(fragmentManager, TAG);
+        dialogFragment.mShowTime = System.currentTimeMillis();
+        dialogFragment.setCancelable(false);
+
+        return dialogFragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setRetainInstance(true);
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Create the progress dialog and set its properties
+        final ProgressDialog dialog = new ProgressDialog(getActivity());
+        dialog.setIndeterminate(true);
+        dialog.setIndeterminateDrawable(null);
+        dialog.setTitle(mTitle);
+        dialog.setMessage(mMessage);
+
+        return dialog;
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mActivityReady = true;
+
+        // Check if superDismiss() had been called before.  This can happen if in a long
+        // running operation, the user hits the home button and closes this fragment's activity.
+        // Upon returning, we want to dismiss this progress dialog fragment.
+        if (mCalledSuperDismiss) {
+            superDismiss();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mActivityReady = false;
+    }
+
+    /**
+     * There is a race condition that is not handled properly by the DialogFragment class.
+     * If we don't check that this onDismiss callback isn't for the old progress dialog from before
+     * the device orientation change, then this will cause the newly created dialog after the
+     * orientation change to be dismissed immediately.
+     */
+    @Override
+    public void onDismiss(DialogInterface dialog) {
+        if (mOldDialog != null && mOldDialog == dialog) {
+            // This is the callback from the old progress dialog that was already dismissed before
+            // the device orientation change, so just ignore it.
+            return;
+        }
+        super.onDismiss(dialog);
+    }
+
+    /**
+     * Save the old dialog that is about to get destroyed in case this is due to a change
+     * in device orientation.  This will allow us to intercept the callback to
+     * {@link #onDismiss(DialogInterface)} in case the callback happens after a new progress dialog
+     * instance was created.
+     */
+    @Override
+    public void onDestroyView() {
+        mOldDialog = getDialog();
+        super.onDestroyView();
+    }
+
+    /**
+     * This tells the progress dialog to dismiss itself after guaranteeing to be shown for the
+     * specified time in {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
+     */
+    @Override
+    public void dismiss() {
+        mAllowStateLoss = false;
+        dismissWhenReady();
+    }
+
+    /**
+     * This tells the progress dialog to dismiss itself (with state loss) after guaranteeing to be
+     * shown for the specified time in
+     * {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
+     */
+    @Override
+    public void dismissAllowingStateLoss() {
+        mAllowStateLoss = true;
+        dismissWhenReady();
+    }
+
+    /**
+     * Tells the progress dialog to dismiss itself after guaranteeing that the dialog had been
+     * showing for at least the minimum display time as set in
+     * {@link #show(FragmentManager, CharSequence, CharSequence, long)}.
+     */
+    private void dismissWhenReady() {
+        // Compute how long the dialog has been showing
+        final long shownTime = System.currentTimeMillis() - mShowTime;
+        if (shownTime >= mMinDisplayTime) {
+            // dismiss immediately
+            mHandler.post(mDismisser);
+        } else {
+            // Need to wait some more, so compute the amount of time to sleep.
+            final long sleepTime = mMinDisplayTime - shownTime;
+            mHandler.postDelayed(mDismisser, sleepTime);
+        }
+    }
+
+    /**
+     * Actually dismiss the dialog fragment.
+     */
+    private void superDismiss() {
+        mCalledSuperDismiss = true;
+        if (mActivityReady) {
+            // The fragment is either in onStart or past it, but has not gotten to onStop yet.
+            // It is safe to dismiss this dialog fragment.
+            if (mAllowStateLoss) {
+                super.dismissAllowingStateLoss();
+            } else {
+                super.dismiss();
+            }
+        }
+        // If mActivityReady is false, then this dialog fragment has already passed the onStop
+        // state. This can happen if the user hit the 'home' button before this dialog fragment was
+        // dismissed or if there is a configuration change.
+        // In the event that this dialog fragment is re-attached and reaches onStart (e.g.,
+        // because the user returns to this fragment's activity or the device configuration change
+        // has re-attached this dialog fragment), because the mCalledSuperDismiss flag was set to
+        // true, this dialog fragment will be dismissed within onStart.  So, there's nothing else
+        // that needs to be done.
+    }
+}
diff --git a/src/com/android/contacts/common/format/FormatUtils.java b/src/com/android/contacts/common/format/FormatUtils.java
new file mode 100644
index 0000000..6a274de
--- /dev/null
+++ b/src/com/android/contacts/common/format/FormatUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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.common.format;
+
+import android.database.CharArrayBuffer;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.style.StyleSpan;
+
+import com.google.common.annotations.VisibleForTesting;
+
+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.
+     */
+    @VisibleForTesting
+    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;
+    }
+
+    @VisibleForTesting
+    public static void copyToCharArrayBuffer(String text, CharArrayBuffer buffer) {
+        if (text != null) {
+            char[] data = buffer.data;
+            if (data == null || data.length < text.length()) {
+                buffer.data = text.toCharArray();
+            } else {
+                text.getChars(0, text.length(), data, 0);
+            }
+            buffer.sizeCopied = text.length();
+        } else {
+            buffer.sizeCopied = 0;
+        }
+    }
+
+    /** Returns a String that represents the content of the given {@link CharArrayBuffer}. */
+    @VisibleForTesting
+    public static String charArrayBufferToString(CharArrayBuffer buffer) {
+        return new String(buffer.data, 0, buffer.sizeCopied);
+    }
+
+    /**
+     * Finds the index of the first word that starts with the given prefix.
+     * <p>
+     * If not found, returns -1.
+     *
+     * @param text the text in which to search for the prefix
+     * @param prefix the text to find, in upper case letters
+     */
+    public static int indexOfWordPrefix(CharSequence text, char[] prefix) {
+        if (prefix == null || text == null) {
+            return -1;
+        }
+
+        int textLength = text.length();
+        int prefixLength = prefix.length;
+
+        if (prefixLength == 0 || textLength < prefixLength) {
+            return -1;
+        }
+
+        int i = 0;
+        while (i < textLength) {
+            // Skip non-word characters
+            while (i < textLength && !Character.isLetterOrDigit(text.charAt(i))) {
+                i++;
+            }
+
+            if (i + prefixLength > textLength) {
+                return -1;
+            }
+
+            // Compare the prefixes
+            int j;
+            for (j = 0; j < prefixLength; j++) {
+                if (Character.toUpperCase(text.charAt(i + j)) != prefix[j]) {
+                    break;
+                }
+            }
+            if (j == prefixLength) {
+                return i;
+            }
+
+            // Skip this word
+            while (i < textLength && Character.isLetterOrDigit(text.charAt(i))) {
+                i++;
+            }
+        }
+
+        return -1;
+    }
+
+}
diff --git a/src/com/android/contacts/common/format/PrefixHighlighter.java b/src/com/android/contacts/common/format/PrefixHighlighter.java
new file mode 100644
index 0000000..65edf58
--- /dev/null
+++ b/src/com/android/contacts/common/format/PrefixHighlighter.java
@@ -0,0 +1,66 @@
+/*
+ * 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.common.format;
+
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.widget.TextView;
+
+/**
+ * Highlights the text in a text field.
+ */
+public class PrefixHighlighter {
+    private final int mPrefixHighlightColor;
+
+    private ForegroundColorSpan mPrefixColorSpan;
+
+    public PrefixHighlighter(int prefixHighlightColor) {
+        mPrefixHighlightColor = prefixHighlightColor;
+    }
+
+    /**
+     * Sets the text on the given text view, highlighting the word that matches the given prefix.
+     *
+     * @param view the view on which to set the text
+     * @param text the string to use as the text
+     * @param prefix the prefix to look for
+     */
+    public void setText(TextView view, String text, char[] prefix) {
+        view.setText(apply(text, prefix));
+    }
+
+    /**
+     * Returns a CharSequence which highlights the given prefix if found in the given text.
+     *
+     * @param text the text to which to apply the highlight
+     * @param prefix the prefix to look for
+     */
+    public CharSequence apply(CharSequence text, char[] prefix) {
+        int index = FormatUtils.indexOfWordPrefix(text, prefix);
+        if (index != -1) {
+            if (mPrefixColorSpan == null) {
+                mPrefixColorSpan = new ForegroundColorSpan(mPrefixHighlightColor);
+            }
+
+            SpannableString result = new SpannableString(text);
+            result.setSpan(mPrefixColorSpan, index, index + prefix.length, 0 /* flags */);
+            return result;
+        } else {
+            return text;
+        }
+    }
+}
diff --git a/src/com/android/contacts/common/format/SpannedTestUtils.java b/src/com/android/contacts/common/format/SpannedTestUtils.java
new file mode 100644
index 0000000..8c2a22d
--- /dev/null
+++ b/src/com/android/contacts/common/format/SpannedTestUtils.java
@@ -0,0 +1,83 @@
+/*
+ * 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.common.format;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.widget.TextView;
+
+import junit.framework.Assert;
+
+/**
+ * Utility class to check the value of spanned text in text views.
+ */
+@SmallTest
+public class SpannedTestUtils {
+    /**
+     * Checks that the text contained in the text view matches the given HTML text.
+     *
+     * @param expectedHtmlText the expected text to be in the text view
+     * @param textView the text view from which to get the text
+     */
+    public static void checkHtmlText(String expectedHtmlText, TextView textView) {
+        String actualHtmlText = Html.toHtml((Spanned) textView.getText());
+        if (TextUtils.isEmpty(expectedHtmlText)) {
+            // If the text is empty, it does not add the <p></p> bits to it.
+            Assert.assertEquals("", actualHtmlText);
+        } else {
+            Assert.assertEquals("<p dir=ltr>" + expectedHtmlText + "</p>\n", actualHtmlText);
+        }
+    }
+
+
+    /**
+     * Assert span exists in the correct location.
+     *
+     * @param seq The spannable string to check.
+     * @param start The starting index.
+     * @param end The ending index.
+     */
+    public static void assertPrefixSpan(CharSequence seq, int start, int end) {
+        Assert.assertTrue(seq instanceof Spanned);
+        Spanned spannable = (Spanned) seq;
+
+        if (start > 0) {
+            Assert.assertEquals(0, getNumForegroundColorSpansBetween(spannable, 0, start - 1));
+        }
+        Assert.assertEquals(1, getNumForegroundColorSpansBetween(spannable, start, end));
+        Assert.assertEquals(0, getNumForegroundColorSpansBetween(spannable, end + 1,
+                spannable.length() - 1));
+    }
+
+    private static int getNumForegroundColorSpansBetween(Spanned value, int start, int end) {
+        return value.getSpans(start, end, ForegroundColorSpan.class).length;
+    }
+
+    /**
+     * Asserts that the given character sequence is not a Spanned object and text is correct.
+     *
+     * @param seq The sequence to check.
+     * @param expected The expected text.
+     */
+    public static void assertNotSpanned(CharSequence seq, String expected) {
+        Assert.assertFalse(seq instanceof Spanned);
+        Assert.assertEquals(expected, seq);
+    }
+}
diff --git a/tests/src/com/android/contacts/common/format/FormatUtilsTests.java b/tests/src/com/android/contacts/common/format/FormatUtilsTests.java
new file mode 100644
index 0000000..b38019d
--- /dev/null
+++ b/tests/src/com/android/contacts/common/format/FormatUtilsTests.java
@@ -0,0 +1,114 @@
+/*
+ * 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.common.format;
+
+import android.database.CharArrayBuffer;
+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"));
+    }
+
+    public void testCopyToCharArrayBuffer() {
+        CharArrayBuffer charArrayBuffer = new CharArrayBuffer(20);
+        checkCopyToCharArrayBuffer(charArrayBuffer, null, 0);
+        checkCopyToCharArrayBuffer(charArrayBuffer, "", 0);
+        checkCopyToCharArrayBuffer(charArrayBuffer, "test", 4);
+        // Check that it works after copying something into it.
+        checkCopyToCharArrayBuffer(charArrayBuffer, "", 0);
+        checkCopyToCharArrayBuffer(charArrayBuffer, "test", 4);
+        checkCopyToCharArrayBuffer(charArrayBuffer, null, 0);
+        // This requires a resize of the actual buffer.
+        checkCopyToCharArrayBuffer(charArrayBuffer, "test test test test test", 24);
+    }
+
+    public void testCharArrayBufferToString() {
+        checkCharArrayBufferToString("");
+        checkCharArrayBufferToString("test");
+        checkCharArrayBufferToString("test test test test test");
+    }
+
+    /** Checks that copying a string into a {@link CharArrayBuffer} and back works correctly. */
+    private void checkCharArrayBufferToString(String text) {
+        CharArrayBuffer buffer = new CharArrayBuffer(20);
+        FormatUtils.copyToCharArrayBuffer(text, buffer);
+        assertEquals(text, FormatUtils.charArrayBufferToString(buffer));
+    }
+
+    /**
+     * Checks that copying into the char array buffer copies the values correctly.
+     */
+    private void checkCopyToCharArrayBuffer(CharArrayBuffer buffer, String value, int length) {
+        FormatUtils.copyToCharArrayBuffer(value, buffer);
+        assertEquals(length, buffer.sizeCopied);
+        for (int index = 0; index < length; ++index) {
+            assertEquals(value.charAt(index), buffer.data[index]);
+        }
+    }
+
+    public void testIndexOfWordPrefix_NullPrefix() {
+        assertEquals(-1, FormatUtils.indexOfWordPrefix("test", null));
+    }
+
+    public void testIndexOfWordPrefix_NullText() {
+        assertEquals(-1, FormatUtils.indexOfWordPrefix(null, "TE".toCharArray()));
+    }
+
+    public void testIndexOfWordPrefix_MatchingPrefix() {
+        checkIndexOfWordPrefix("test", "TE", 0);
+        checkIndexOfWordPrefix("Test", "TE", 0);
+        checkIndexOfWordPrefix("TEst", "TE", 0);
+        checkIndexOfWordPrefix("TEST", "TE", 0);
+        checkIndexOfWordPrefix("a test", "TE", 2);
+        checkIndexOfWordPrefix("test test", "TE", 0);
+        checkIndexOfWordPrefix("a test test", "TE", 2);
+    }
+
+    public void testIndexOfWordPrefix_NotMatchingPrefix() {
+        checkIndexOfWordPrefix("test", "TA", -1);
+        checkIndexOfWordPrefix("test type theme", "TA", -1);
+        checkIndexOfWordPrefix("atest retest pretest", "TEST", -1);
+        checkIndexOfWordPrefix("tes", "TEST", -1);
+    }
+
+    public void testIndexOfWordPrefix_LowerCase() {
+        // The prefix match only works if the prefix is un upper case.
+        checkIndexOfWordPrefix("test", "te", -1);
+    }
+
+    /**
+     * Checks that getting the index of a word prefix in the given text returns the expected index.
+     *
+     * @param text the text in which to look for the word
+     * @param wordPrefix the word prefix to look for
+     * @param expectedIndex the expected value to be returned by the function
+     */
+    private void checkIndexOfWordPrefix(String text, String wordPrefix, int expectedIndex) {
+        assertEquals(expectedIndex, FormatUtils.indexOfWordPrefix(text, wordPrefix.toCharArray()));
+    }
+}
diff --git a/tests/src/com/android/contacts/common/format/PrefixHighligherTest.java b/tests/src/com/android/contacts/common/format/PrefixHighligherTest.java
new file mode 100644
index 0000000..d57e595
--- /dev/null
+++ b/tests/src/com/android/contacts/common/format/PrefixHighligherTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.common.format;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link com.android.contacts.common.format.PrefixHighlighter}.
+ */
+@SmallTest
+public class PrefixHighligherTest extends TestCase {
+    private static final int TEST_PREFIX_HIGHLIGHT_COLOR = 0xFF0000;
+
+    /** The object under test. */
+    private PrefixHighlighter mPrefixHighlighter;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPrefixHighlighter = new PrefixHighlighter(TEST_PREFIX_HIGHLIGHT_COLOR);
+    }
+
+    public void testApply_EmptyPrefix() {
+        CharSequence seq = mPrefixHighlighter.apply("", new char[0]);
+        SpannedTestUtils.assertNotSpanned(seq, "");
+
+        seq = mPrefixHighlighter.apply("test", new char[0]);
+        SpannedTestUtils.assertNotSpanned(seq, "test");
+    }
+
+    public void testSetText_MatchingPrefix() {
+        final char[] charArray = "TE".toCharArray();
+
+        CharSequence seq = mPrefixHighlighter.apply("test", charArray);
+        SpannedTestUtils.assertPrefixSpan(seq, 0, 1);
+
+        seq = mPrefixHighlighter.apply("Test", charArray);
+        SpannedTestUtils.assertPrefixSpan(seq, 0, 1);
+
+        seq = mPrefixHighlighter.apply("TEst", charArray);
+        SpannedTestUtils.assertPrefixSpan(seq, 0, 1);
+
+        seq = mPrefixHighlighter.apply("a test", charArray);
+        SpannedTestUtils.assertPrefixSpan(seq, 2, 3);
+    }
+
+    public void testSetText_NotMatchingPrefix() {
+        final CharSequence seq = mPrefixHighlighter.apply("test", "TA".toCharArray());
+        SpannedTestUtils.assertNotSpanned(seq, "test");
+    }
+
+    public void testSetText_FirstMatch() {
+        final CharSequence seq = mPrefixHighlighter.apply("a test's tests are not tests",
+                "TE".toCharArray());
+        SpannedTestUtils.assertPrefixSpan(seq, 2, 3);
+    }
+
+    public void testSetText_NoMatchingMiddleOfWord() {
+        final char[] charArray = "TE".toCharArray();
+        CharSequence seq = mPrefixHighlighter.apply("atest", charArray);
+        SpannedTestUtils.assertNotSpanned(seq, "atest");
+
+        seq = mPrefixHighlighter.apply("atest otest", charArray);
+        SpannedTestUtils.assertNotSpanned(seq, "atest otest");
+
+        seq = mPrefixHighlighter.apply("atest test", charArray);
+        SpannedTestUtils.assertPrefixSpan(seq, 6, 7);
+    }
+}