Extract a couple of utility classes for formatting.
The PrefixHighlighter class handles highlighting of a prefix in a text
view.
The DisplayNameFormatter handles both the bold of the first name and the
highlight needed when searching (using the PrefixHighlighter above).
Change-Id: Ibd1d3ecd6a88ce40de6dbc591af21c1e7260511e
diff --git a/src/com/android/contacts/format/DisplayNameFormatter.java b/src/com/android/contacts/format/DisplayNameFormatter.java
new file mode 100644
index 0000000..05698f8
--- /dev/null
+++ b/src/com/android/contacts/format/DisplayNameFormatter.java
@@ -0,0 +1,106 @@
+/*
+ * 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 com.android.contacts.widget.TextWithHighlighting;
+import com.android.contacts.widget.TextWithHighlightingFactory;
+
+import android.database.CharArrayBuffer;
+import android.graphics.Typeface;
+import android.provider.ContactsContract;
+import android.text.Spannable;
+import android.widget.TextView;
+
+import java.util.Arrays;
+
+/**
+ * Sets the content of the given text view, to contain the formatted display name, with a
+ * prefix if necessary.
+ */
+public final class DisplayNameFormatter {
+ private final CharArrayBuffer mNameBuffer = new CharArrayBuffer(128);
+ private final CharArrayBuffer mAlternateNameBuffer = new CharArrayBuffer(128);
+ private final PrefixHighlighter mPrefixHighlighter;
+
+ private TextWithHighlightingFactory mTextWithHighlightingFactory;
+ private TextWithHighlighting mTextWithHighlighting;
+ private CharSequence mUnknownNameText;
+
+ public DisplayNameFormatter(PrefixHighlighter prefixHighlighter) {
+ mPrefixHighlighter = prefixHighlighter;
+ }
+
+ public CharArrayBuffer getNameBuffer() {
+ return mNameBuffer;
+ }
+
+ public CharArrayBuffer getAlternateNameBuffer() {
+ return mAlternateNameBuffer;
+ }
+
+ public void setTextWithHighlightingFactory(TextWithHighlightingFactory factory) {
+ mTextWithHighlightingFactory = factory;
+ }
+
+ public void setUnknownNameText(CharSequence unknownNameText) {
+ mUnknownNameText = unknownNameText;
+ }
+
+ public void setDisplayName(TextView view, int displayOrder,
+ boolean highlightingEnabled, char[] highlightedPrefix) {
+ // Compute the point at which name and alternate name overlap (for bolding).
+ int overlapPoint = FormatUtils.overlapPoint(mNameBuffer, mAlternateNameBuffer);
+ int boldStart = 0;
+ int boldEnd = overlapPoint;
+ if (displayOrder == ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE) {
+ boldStart = overlapPoint;
+ boldEnd = mNameBuffer.sizeCopied;
+ }
+
+ int size = mNameBuffer.sizeCopied;
+ if (size != 0) {
+ if (highlightedPrefix != null) {
+ mPrefixHighlighter.setText(view, mNameBuffer, highlightedPrefix);
+ } else if (highlightingEnabled) {
+ if (mTextWithHighlighting == null) {
+ mTextWithHighlighting =
+ mTextWithHighlightingFactory.createTextWithHighlighting();
+ }
+ mTextWithHighlighting.setText(mNameBuffer, mAlternateNameBuffer);
+ if (overlapPoint > 0) {
+ // Bold the first name.
+ view.setText(FormatUtils.applyStyleToSpan(Typeface.BOLD,
+ mTextWithHighlighting, boldStart, boldEnd,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE));
+ } else {
+ view.setText(mTextWithHighlighting);
+ }
+ } else {
+ if (overlapPoint > 0) {
+ // Bold the first name.
+ view.setText(FormatUtils.applyStyleToSpan(Typeface.BOLD,
+ new String(Arrays.copyOfRange(mNameBuffer.data, 0, size)),
+ boldStart, boldEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE));
+ } else {
+ view.setText(mNameBuffer.data, 0, size);
+ }
+ }
+ } else {
+ view.setText(mUnknownNameText);
+ }
+ }
+}
diff --git a/src/com/android/contacts/format/FormatUtils.java b/src/com/android/contacts/format/FormatUtils.java
index ac34d6d..757e235 100644
--- a/src/com/android/contacts/format/FormatUtils.java
+++ b/src/com/android/contacts/format/FormatUtils.java
@@ -105,4 +105,64 @@
text.setSpan(new StyleSpan(style), start, end, flags);
return text;
}
+
+ 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;
+ }
+ }
+
+ /**
+ * Finds the index of the first word that starts with the given prefix.
+ * <p>
+ * If not found, returns -1.
+ */
+ public static int indexOfWordPrefix(CharArrayBuffer buffer, char[] prefix) {
+ if (prefix == null || prefix.length == 0) {
+ return -1;
+ }
+
+ char[] string1 = buffer.data;
+ int bufferSize = buffer.sizeCopied;
+ int prefixSize = prefix.length;
+
+ int i = 0;
+ while (i < bufferSize) {
+
+ // Skip non-word characters
+ while (i < bufferSize && !Character.isLetterOrDigit(string1[i])) {
+ i++;
+ }
+
+ if (i + prefixSize > bufferSize) {
+ return -1;
+ }
+
+ // Compare the prefixes
+ int j;
+ for (j = 0; j < prefixSize; j++) {
+ if (Character.toUpperCase(string1[i+j]) != prefix[j]) {
+ break;
+ }
+ }
+ if (j == prefixSize) {
+ return i;
+ }
+
+ // Skip this word
+ while (i < bufferSize && Character.isLetterOrDigit(string1[i])) {
+ i++;
+ }
+ }
+
+ return -1;
+ }
}
diff --git a/src/com/android/contacts/format/PrefixHighlighter.java b/src/com/android/contacts/format/PrefixHighlighter.java
new file mode 100644
index 0000000..fce1d4b
--- /dev/null
+++ b/src/com/android/contacts/format/PrefixHighlighter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.widget.TextView;
+
+/**
+ * Highlights the text in a text field.
+ */
+public class PrefixHighlighter {
+ private final CharArrayBuffer mBuffer = new CharArrayBuffer(128);
+ 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) {
+ FormatUtils.copyToCharArrayBuffer(text, mBuffer);
+ setText(view, mBuffer, prefix);
+ }
+
+ /**
+ * 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 text to use as the text
+ * @param prefix the prefix to look for
+ */
+ public void setText(TextView view, CharArrayBuffer text, char[] prefix) {
+ int index = FormatUtils.indexOfWordPrefix(text, prefix);
+ if (index != -1) {
+ if (mPrefixColorSpan == null) {
+ mPrefixColorSpan = new ForegroundColorSpan(mPrefixHighlightColor);
+ }
+
+ String string = new String(text.data, 0, text.sizeCopied);
+ SpannableString name = new SpannableString(
+ FormatUtils.applyStyleToSpan(Typeface.BOLD, string, 0, index,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE));
+ name.setSpan(mPrefixColorSpan, index, index + prefix.length, 0 /* flags */);
+ view.setText(name);
+ } else {
+ view.setText(text.data, 0, text.sizeCopied);
+ }
+ }
+}
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 022c2f6..bc81cd3 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -18,8 +18,8 @@
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.format.DisplayNameFormatter;
+import com.android.contacts.format.PrefixHighlighter;
import com.android.contacts.widget.TextWithHighlightingFactory;
import android.content.Context;
@@ -31,14 +31,9 @@
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;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
@@ -49,8 +44,6 @@
import android.widget.QuickContactBadge;
import android.widget.TextView;
-import java.util.Arrays;
-
/**
* A custom view for an item in the contact list.
*/
@@ -73,7 +66,6 @@
private final int mGapBetweenLabelAndData;
private final int mCallButtonPadding;
private final int mPresenceIconMargin;
- private final int mPrefixHightlightColor;
private final int mHeaderTextColor;
private final int mHeaderTextIndent;
private final int mHeaderTextSize;
@@ -115,21 +107,18 @@
private int mLine4Height;
private OnClickListener mCallButtonClickListener;
- private TextWithHighlightingFactory mTextWithHighlightingFactory;
- private CharArrayBuffer mNameBuffer = new CharArrayBuffer(128);
private CharArrayBuffer mDataBuffer = new CharArrayBuffer(128);
- private CharArrayBuffer mHighlightedTextBuffer = new CharArrayBuffer(128);
- private TextWithHighlighting mTextWithHighlighting;
private CharArrayBuffer mPhoneticNameBuffer = new CharArrayBuffer(128);
- private CharSequence mUnknownNameText;
-
private boolean mActivatedStateSupported;
- private ForegroundColorSpan mPrefixColorSpan;
-
private Rect mBoundsWithoutHeader = new Rect();
+ /** A helper used to highlight a prefix in a text field. */
+ private PrefixHighlighter mPrefixHighligher;
+ /** A helper used to format display names. */
+ private DisplayNameFormatter mDisplayNameFormatter;
+
/**
* Special class to allow the parent to be pressed without being pressed itself.
* This way the line of a tab can be pressed, but the image itself is not.
@@ -184,9 +173,6 @@
R.styleable.ContactListItemView_list_item_presence_icon_margin, 0);
mDefaultPhotoViewSize = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_photo_size, 0);
- mPrefixHightlightColor = a.getColor(
- R.styleable.ContactListItemView_list_item_prefix_highlight_color, Color.GREEN);
-
mHeaderTextIndent = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_header_text_indent, 0);
mHeaderTextColor = a.getColor(
@@ -194,6 +180,10 @@
mHeaderTextSize = a.getDimensionPixelSize(
R.styleable.ContactListItemView_list_item_header_text_size, 12);
+ mPrefixHighligher = new PrefixHighlighter(
+ a.getColor(R.styleable.ContactListItemView_list_item_prefix_highlight_color,
+ Color.GREEN));
+
a.recycle();
mHeaderBackgroundHeight = mHeaderBackgroundDrawable.getIntrinsicHeight();
@@ -202,6 +192,8 @@
if (mActivatedBackgroundDrawable != null) {
mActivatedBackgroundDrawable.setCallback(this);
}
+
+ mDisplayNameFormatter = new DisplayNameFormatter(mPrefixHighligher);
}
/**
@@ -212,11 +204,11 @@
}
public void setTextWithHighlightingFactory(TextWithHighlightingFactory factory) {
- mTextWithHighlightingFactory = factory;
+ mDisplayNameFormatter.setTextWithHighlightingFactory(factory);
}
public void setUnknownNameText(CharSequence unknownNameText) {
- mUnknownNameText = unknownNameText;
+ mDisplayNameFormatter.setUnknownNameText(unknownNameText);
}
public void setQuickContactEnabled(boolean flag) {
@@ -767,8 +759,7 @@
mSnippetView.setVisibility(View.GONE);
}
} else {
- getSnippetView();
- setTextWithPrefixHighlighting(mSnippetView, text);
+ mPrefixHighligher.setText(getSnippetView(), text, mHighlightedPrefix);
mSnippetView.setVisibility(VISIBLE);
}
}
@@ -813,51 +804,13 @@
public void showDisplayName(Cursor cursor, int nameColumnIndex, int alternativeNameColumnIndex,
boolean highlightingEnabled, int displayOrder) {
+ // Copy out the display name and alternate display name.
+ cursor.copyStringToBuffer(nameColumnIndex, mDisplayNameFormatter.getNameBuffer());
+ cursor.copyStringToBuffer(alternativeNameColumnIndex,
+ mDisplayNameFormatter.getAlternateNameBuffer());
- // 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) {
- if (mHighlightedPrefix != null) {
- setTextWithPrefixHighlighting(nameView, mNameBuffer);
- } else if (highlightingEnabled) {
- if (mTextWithHighlighting == null) {
- mTextWithHighlighting =
- mTextWithHighlightingFactory.createTextWithHighlighting();
- }
- mTextWithHighlighting.setText(mNameBuffer, mHighlightedTextBuffer);
- 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 {
- 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);
- }
+ mDisplayNameFormatter.setDisplayName(
+ getNameTextView(), displayOrder, highlightingEnabled, mHighlightedPrefix);
}
public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
@@ -942,85 +895,6 @@
this.mActivatedStateSupported = flag;
}
- /**
- * Sets text on the given text view, highlighting the word that matches
- * the given prefix (see {@link #setHighlightedPrefix}).
- */
- private void setTextWithPrefixHighlighting(TextView textView, String text) {
- mHighlightedTextBuffer.sizeCopied =
- Math.min(text.length(), mHighlightedTextBuffer.data.length);
- text.getChars(0, mHighlightedTextBuffer.sizeCopied, mHighlightedTextBuffer.data, 0);
- setTextWithPrefixHighlighting(textView, mHighlightedTextBuffer);
- }
-
- /**
- * Sets text on the given text view, highlighting the word that matches
- * the given prefix (see {@link #setHighlightedPrefix}).
- */
- private void setTextWithPrefixHighlighting(TextView textView, CharArrayBuffer text) {
- int index = indexOfWordPrefix(text, mHighlightedPrefix);
- if (index != -1) {
- if (mPrefixColorSpan == null) {
- mPrefixColorSpan = new ForegroundColorSpan(mPrefixHightlightColor);
- }
-
- String string = new String(text.data, 0, text.sizeCopied);
- 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 {
- textView.setText(text.data, 0, text.sizeCopied);
- }
- }
-
- /**
- * Finds the index of the word that starts with the given prefix. If not found,
- * returns -1.
- */
- private int indexOfWordPrefix(CharArrayBuffer buffer, char[] prefix) {
- if (prefix == null || prefix.length == 0) {
- return -1;
- }
-
- char[] string1 = buffer.data;
- int count1 = buffer.sizeCopied;
- int count2 = prefix.length;
-
- int size = count2;
- int i = 0;
- while (i < count1) {
-
- // Skip non-word characters
- while (i < count1 && !Character.isLetterOrDigit(string1[i])) {
- i++;
- }
-
- if (i + size > count1) {
- return -1;
- }
-
- // Compare the prefixes
- int j;
- for (j = 0; j < size; j++) {
- if (Character.toUpperCase(string1[i+j]) != prefix[j]) {
- break;
- }
- }
- if (j == size) {
- return i;
- }
-
- // Skip this word
- while (i < count1 && Character.isLetterOrDigit(string1[i])) {
- i++;
- }
- }
-
- return -1;
- }
-
@Override
public void requestLayout() {
// We will assume that once measured this will not need to resize
diff --git a/tests/src/com/android/contacts/format/DisplayNameFormatterTest.java b/tests/src/com/android/contacts/format/DisplayNameFormatterTest.java
new file mode 100644
index 0000000..a7843c1
--- /dev/null
+++ b/tests/src/com/android/contacts/format/DisplayNameFormatterTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.provider.ContactsContract;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+/**
+ * Unit tests for {@link DisplayNameFormatter}.
+ */
+@SmallTest
+public class DisplayNameFormatterTest extends AndroidTestCase {
+ private static final int TEST_PREFIX_HIGHLIGHT_COLOR = 0xFF0000;
+
+ private PrefixHighlighter mPrefixHighlighter;
+ /** The object under test. */
+ private DisplayNameFormatter mDisplayNameFormatter;
+ /** The view to on which the text is set. */
+ private TextView mView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPrefixHighlighter = new PrefixHighlighter(TEST_PREFIX_HIGHLIGHT_COLOR);
+ mDisplayNameFormatter = new DisplayNameFormatter(mPrefixHighlighter);
+ mView = new TextView(getContext());
+ // This guarantees that the text will be stored as a Spannable so that we can determine
+ // which styles have been applied to it.
+ mView.setText("", TextView.BufferType.SPANNABLE);
+ }
+
+ public void testSetDisplayName_Simple() {
+ setNames("John Doe", "Doe John");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("<b>John </b>Doe", mView);
+ setNames("Jean Pierre Doe", "Doe Jean Pierre");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("<b>Jean Pierre </b>Doe", mView);
+ setNames("John Doe Smith", "Doe Smith John");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("<b>John </b>Doe Smith", mView);
+ }
+ public void testSetDisplayName_AccidentalOverlap() {
+ // This is probably not what we want, but we assume that the two names differ only in the
+ // order in which the two components are listed.
+ setNames("Johnson John", "Johnson Smith");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("<b>Johnson </b>John", mView);
+ }
+
+ public void testSetDisplayName_Reversed() {
+ setNames("John Doe", "Doe John");
+ setDisplayNameReversed();
+ SpannedTestUtils.checkHtmlText("John <b>Doe</b>", mView);
+ setNames("Jean Pierre Doe", "Doe Jean Pierre");
+ setDisplayNameReversed();
+ SpannedTestUtils.checkHtmlText("Jean Pierre <b>Doe</b>", mView);
+ setNames("John Doe Smith", "Doe Smith John");
+ setDisplayNameReversed();
+ SpannedTestUtils.checkHtmlText("John <b>Doe Smith</b>", mView);
+ }
+
+ public void testSetDisplayName_NoOverlap() {
+ setNames("John Smith", "Doe Albert");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("John Smith", mView);
+ }
+
+ public void testSetDisplayName_Prefix() {
+ setNames("John Doe", "Doe John");
+ setDisplayNameWithPrefix("DO");
+ SpannedTestUtils.checkHtmlText("<b>John </b><font color =\"#1ff0000\">Do</font>e", mView);
+ }
+
+ public void testSetDisplayName_Empty() {
+ setNames("", "");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("", mView);
+ }
+
+ public void testSetDisplayName_Unknown() {
+ mDisplayNameFormatter.setUnknownNameText("unknown");
+ setNames("", "");
+ setDisplayName();
+ SpannedTestUtils.checkHtmlText("unknown", mView);
+ }
+
+ public void testSetDisplayName_Highlighting() {
+ mDisplayNameFormatter.setTextWithHighlightingFactory(new TestTextWithHighlightingFactory());
+ setNames("John Doe", "Sue Anne");
+ setDisplayNameWithHighlighting();
+ SpannedTestUtils.checkHtmlText("<i>John Doe</i> <i>Sue Anne</i>", mView);
+ }
+
+ public void testSetDisplayName_HighlightingAndBoldFirstName() {
+ mDisplayNameFormatter.setTextWithHighlightingFactory(new TestTextWithHighlightingFactory());
+ setNames("John Doe", "Doe John");
+ setDisplayNameWithHighlighting();
+ SpannedTestUtils.checkHtmlText("<i><b>John </b></i><i>Doe</i> <i>Doe John</i>", mView);
+ }
+
+ /**
+ * Sets the name and alternate name on the formatter.
+ *
+ * @param name the name to be display
+ * @param alternateName the alternate name to be displayed
+ */
+ private void setNames(String name, String alternateName) {
+ FormatUtils.copyToCharArrayBuffer(name, mDisplayNameFormatter.getNameBuffer());
+ FormatUtils.copyToCharArrayBuffer(alternateName,
+ mDisplayNameFormatter.getAlternateNameBuffer());
+ }
+
+ /**
+ * Sets the display name on the text view.
+ */
+ private void setDisplayName() {
+ mDisplayNameFormatter.setDisplayName(mView,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY, false, null);
+ }
+
+ /**
+ * Sets the display name on the text view using the reverted order.
+ */
+ private void setDisplayNameReversed() {
+ mDisplayNameFormatter.setDisplayName(mView,
+ ContactsContract.Preferences.DISPLAY_ORDER_ALTERNATIVE, false, null);
+ }
+
+ /**
+ * Sets the display name on the text view with prefix highlighting enabled.
+ */
+ private void setDisplayNameWithPrefix(String prefix) {
+ mDisplayNameFormatter.setDisplayName(mView,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY, false, prefix.toCharArray());
+ }
+
+ /**
+ * Sets the display name on the text view with highlighting enabled.
+ */
+ private void setDisplayNameWithHighlighting() {
+ mDisplayNameFormatter.setDisplayName(mView,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY, true, null);
+ }
+}
diff --git a/tests/src/com/android/contacts/format/FormatUtilsTests.java b/tests/src/com/android/contacts/format/FormatUtilsTests.java
index 0464adb..0c1c925 100644
--- a/tests/src/com/android/contacts/format/FormatUtilsTests.java
+++ b/tests/src/com/android/contacts/format/FormatUtilsTests.java
@@ -16,6 +16,7 @@
package com.android.contacts.format;
+import android.database.CharArrayBuffer;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -32,4 +33,64 @@
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);
+ }
+
+ /**
+ * 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_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) {
+ CharArrayBuffer buffer = new CharArrayBuffer(text.length());
+ FormatUtils.copyToCharArrayBuffer(text, buffer);
+ assertEquals(expectedIndex,
+ FormatUtils.indexOfWordPrefix(buffer, wordPrefix.toCharArray()));
+ }
}
diff --git a/tests/src/com/android/contacts/format/PrefixHighligherTest.java b/tests/src/com/android/contacts/format/PrefixHighligherTest.java
new file mode 100644
index 0000000..9ec1c48
--- /dev/null
+++ b/tests/src/com/android/contacts/format/PrefixHighligherTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.text.Spannable;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.widget.TextView;
+
+/**
+ * Unit tests for {@link PrefixHighlighter}.
+ */
+public class PrefixHighligherTest extends AndroidTestCase {
+ private static final int TEST_PREFIX_HIGHLIGHT_COLOR = 0xFF0000;
+
+ /** The object under test. */
+ private PrefixHighlighter mPrefixHighlighter;
+ /** The view to on which the text is set. */
+ private TextView mView;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mPrefixHighlighter = new PrefixHighlighter(TEST_PREFIX_HIGHLIGHT_COLOR);
+ mView = new TextView(getContext());
+ // This guarantees that the text will be stored as a spannable so that we can determine
+ // which styles have been applied to it.
+ mView.setText("", TextView.BufferType.SPANNABLE);
+ }
+
+ public void testSetText_EmptyPrefix() {
+ mPrefixHighlighter.setText(mView, "", new char[0]);
+ checkTextAndNoSpans("");
+ mPrefixHighlighter.setText(mView, "test", new char[0]);
+ checkTextAndNoSpans("test");
+ }
+
+ public void testSetText_MatchingPrefix() {
+ mPrefixHighlighter.setText(mView, "test", "TE".toCharArray());
+ checkTextAndSpan("test", 0, 2);
+ mPrefixHighlighter.setText(mView, "Test", "TE".toCharArray());
+ checkTextAndSpan("Test", 0, 2);
+ mPrefixHighlighter.setText(mView, "TEst", "TE".toCharArray());
+ checkTextAndSpan("TEst", 0, 2);
+ mPrefixHighlighter.setText(mView, "a test", "TE".toCharArray());
+ checkTextAndSpan("a test", 2, 4);
+ }
+
+ public void testSetText_NotMatchingPrefix() {
+ mPrefixHighlighter.setText(mView, "test", "TA".toCharArray());
+ checkTextAndNoSpans("test");
+ }
+
+ public void testSetText_FirstMatch() {
+ mPrefixHighlighter.setText(mView, "a test's tests are not tests", "TE".toCharArray());
+ checkTextAndSpan("a test's tests are not tests", 2, 4);
+ }
+
+ public void testSetText_NoMatchingMiddleOfWord() {
+ mPrefixHighlighter.setText(mView, "atest", "TE".toCharArray());
+ checkTextAndNoSpans("atest");
+ mPrefixHighlighter.setText(mView, "atest otest", "TE".toCharArray());
+ checkTextAndNoSpans("atest otest");
+ mPrefixHighlighter.setText(mView, "atest test", "TE".toCharArray());
+ checkTextAndSpan("atest test", 6, 8);
+ }
+
+ public void testSetText_CharArrayBuffer() {
+ CharArrayBuffer buffer = new CharArrayBuffer(100);
+
+ FormatUtils.copyToCharArrayBuffer("test", buffer);
+ mPrefixHighlighter.setText(mView, buffer, new char[0]);
+ checkTextAndNoSpans("test");
+
+ FormatUtils.copyToCharArrayBuffer("a test", buffer);
+ mPrefixHighlighter.setText(mView, buffer, "TE".toCharArray());
+ checkTextAndSpan("a test", 2, 4);
+
+ FormatUtils.copyToCharArrayBuffer("test", buffer);
+ mPrefixHighlighter.setText(mView, buffer, "TA".toCharArray());
+ checkTextAndNoSpans("test");
+ }
+
+ /**
+ * Checks that the text view contains the given text and there is no highlighted prefix.
+ *
+ * @param expectedText the text expected to be in the view
+ */
+ private void checkTextAndNoSpans(String expectedText) {
+ checkTextAndOptionalSpan(expectedText, false, 0, 0);
+ }
+
+ /**
+ * Checks that the text view contains the given text and the prefix is highlighted at the given
+ * position.
+ *
+ * @param expectedText the text expected to be in the view
+ * @param expectedStart the expect start of the highlighted prefix
+ * @param expectedEnd the expect end of the highlighted prefix
+ */
+ private void checkTextAndSpan(String expectedText, int expectedStart, int expectedEnd) {
+ checkTextAndOptionalSpan(expectedText, true, expectedStart, expectedEnd);
+ }
+
+ /**
+ * Checks that the text view contains the given text and the prefix is highlighted if expected.
+ *
+ * @param expectedText the text expected to be in the view
+ * @param expectedHighlighted whether the prefix should be highlighted in the view
+ * @param expectedStart the expect start of the highlighted prefix
+ * @param expectedEnd the expect end of the highlighted prefix
+ */
+ private void checkTextAndOptionalSpan(String expectedText, boolean expectedHighlighted,
+ int expectedStart, int expectedEnd) {
+ // First check that the text is correct.
+ assertEquals(expectedText, mView.getText().toString());
+ // Get the spannable stored in the text view.
+ Spannable actualText = (Spannable) mView.getText();
+ // Get the style and color spans applied to the text.
+ StyleSpan[] styleSpans = actualText.getSpans(0, expectedText.length(), StyleSpan.class);
+ ForegroundColorSpan[] foregroundColorSpans =
+ actualText.getSpans(0, expectedText.length(), ForegroundColorSpan.class);
+ if (!expectedHighlighted) {
+ // There should be no bold or colored text.
+ assertEquals(0, styleSpans.length);
+ assertEquals(0, foregroundColorSpans.length);
+ } else {
+ // The text up to the found prefix is bold.
+ assertEquals(1, styleSpans.length);
+ StyleSpan boldSpan = styleSpans[0];
+ assertEquals(Typeface.BOLD, boldSpan.getStyle());
+ assertEquals(0, actualText.getSpanStart(boldSpan));
+ assertEquals(expectedStart, actualText.getSpanEnd(boldSpan));
+
+ // The prefix itself is in the highlight color.
+ assertEquals(1, foregroundColorSpans.length);
+ ForegroundColorSpan foregroundColorSpan = foregroundColorSpans[0];
+ assertEquals(TEST_PREFIX_HIGHLIGHT_COLOR, foregroundColorSpan.getForegroundColor());
+ assertEquals(expectedStart, actualText.getSpanStart(foregroundColorSpan));
+ assertEquals(expectedEnd, actualText.getSpanEnd(foregroundColorSpan));
+ }
+ }
+}
diff --git a/tests/src/com/android/contacts/format/SpannedTestUtils.java b/tests/src/com/android/contacts/format/SpannedTestUtils.java
new file mode 100644
index 0000000..625c6aa
--- /dev/null
+++ b/tests/src/com/android/contacts/format/SpannedTestUtils.java
@@ -0,0 +1,46 @@
+/*
+ * 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.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.widget.TextView;
+
+import junit.framework.Assert;
+
+/**
+ * Utility class to check the value of spanned text in text views.
+ */
+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>" + expectedHtmlText + "</p>\n", actualHtmlText);
+ }
+ }
+
+}
diff --git a/tests/src/com/android/contacts/format/TestTextWithHighlightingFactory.java b/tests/src/com/android/contacts/format/TestTextWithHighlightingFactory.java
new file mode 100644
index 0000000..f2848d0
--- /dev/null
+++ b/tests/src/com/android/contacts/format/TestTextWithHighlightingFactory.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.format;
+
+import com.android.contacts.widget.TextWithHighlighting;
+import com.android.contacts.widget.TextWithHighlightingFactory;
+
+import android.database.CharArrayBuffer;
+import android.graphics.Typeface;
+import android.text.SpannableStringBuilder;
+import android.text.style.StyleSpan;
+
+/** A factory for {@link TextWithHighlighting} that wraps its parts in italics. */
+public final class TestTextWithHighlightingFactory implements TextWithHighlightingFactory {
+ /** A {@link TextWithHighlighting} implementation that wraps its parts in italics. */
+ private final static class TestTextWithHighlighting extends SpannableStringBuilder
+ implements TextWithHighlighting {
+ @Override
+ public void setText(CharArrayBuffer baseText, CharArrayBuffer highlightedText) {
+ append(new String(baseText.data, 0, baseText.sizeCopied));
+ append(' ');
+ append(new String(highlightedText.data, 0, highlightedText.sizeCopied));
+ setSpan(new StyleSpan(Typeface.ITALIC), 0, baseText.sizeCopied, 0);
+ setSpan(new StyleSpan(Typeface.ITALIC), baseText.sizeCopied + 1,
+ baseText.sizeCopied + 1 + highlightedText.sizeCopied, 0);
+ }
+ }
+
+ @Override
+ public TextWithHighlighting createTextWithHighlighting() {
+ return new TestTextWithHighlighting();
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/contacts/list/ContactListItemViewTest.java b/tests/src/com/android/contacts/list/ContactListItemViewTest.java
new file mode 100644
index 0000000..3a80e97
--- /dev/null
+++ b/tests/src/com/android/contacts/list/ContactListItemViewTest.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.list;
+
+import com.android.contacts.activities.DialtactsActivity;
+import com.android.contacts.format.SpannedTestUtils;
+import com.android.contacts.format.TestTextWithHighlightingFactory;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.ContactsContract;
+import android.test.ActivityInstrumentationTestCase2;
+import android.widget.TextView;
+
+/**
+ * Unit tests for {@link ContactListItemView}.
+ *
+ * It uses an {@link ActivityInstrumentationTestCase2} for {@link DialtactsActivity} because we need
+ * to have the style properly setup.
+ */
+public class ContactListItemViewTest extends ActivityInstrumentationTestCase2<DialtactsActivity> {
+ public ContactListItemViewTest() {
+ super(DialtactsActivity.class);
+ }
+
+ public void testShowDisplayName_Simple() {
+ Cursor cursor = createCursor("John Doe", "Doe John");
+ ContactListItemView view = createView();
+
+ view.showDisplayName(cursor, 0, 1, false,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+
+ SpannedTestUtils.checkHtmlText("<b>John </b>Doe", view.getNameTextView());
+ }
+
+ public void testShowDisplayName_Unknown() {
+ Cursor cursor = createCursor("", "");
+ ContactListItemView view = createView();
+
+ view.setUnknownNameText("unknown");
+ view.showDisplayName(cursor, 0, 1, false,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+
+ SpannedTestUtils.checkHtmlText("unknown", view.getNameTextView());
+ }
+
+ public void testShowDisplayName_WithPrefix() {
+ Cursor cursor = createCursor("John Doe", "Doe John");
+ ContactListItemView view = createView();
+
+ view.setHighlightedPrefix("DOE".toCharArray());
+ view.showDisplayName(cursor, 0, 1, false,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+
+ SpannedTestUtils.checkHtmlText("<b>John </b><font color =\"#729a27\">Doe</font>",
+ view.getNameTextView());
+ }
+
+ public void testShowDisplayName_WithHighlight() {
+ Cursor cursor = createCursor("John Doe", "Doe John");
+ ContactListItemView view = createView();
+
+ view.setTextWithHighlightingFactory(new TestTextWithHighlightingFactory());
+ view.showDisplayName(cursor, 0, 1, true,
+ ContactsContract.Preferences.DISPLAY_ORDER_PRIMARY);
+
+ SpannedTestUtils.checkHtmlText("<i><b>John </b></i><i>Doe</i> <i>Doe John</i>",
+ view.getNameTextView());
+ }
+
+ public void testSetSnippet_Prefix() {
+ ContactListItemView view = createView();
+ view.setHighlightedPrefix("TEST".toCharArray());
+ view.setSnippet("This is a test");
+ SpannedTestUtils.checkHtmlText("<b>This is a </b><font color =\"#729a27\">test</font>",
+ view.getSnippetView());
+ }
+
+ /** Creates the view to be tested. */
+ private ContactListItemView createView() {
+ ContactListItemView view = new ContactListItemView(getActivity(), null);
+ // Set the name view to use a Spannable to represent its content.
+ view.getNameTextView().setText("", TextView.BufferType.SPANNABLE);
+ return view;
+ }
+
+ /**
+ * Creates a cursor containing a pair of values.
+ *
+ * @param name the name to insert in the first column of the cursor
+ * @param alternateName the alternate name to insert in the second column of the cursor
+ * @return the newly created cursor
+ */
+ private Cursor createCursor(String name, String alternateName) {
+ MatrixCursor cursor = new MatrixCursor(new String[]{"Name", "AlternateName"});
+ cursor.moveToFirst();
+ cursor.addRow(new Object[]{name, alternateName});
+ return cursor;
+ }
+}