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);
+ }
+}