diff options
| author | Chiao Cheng <chiaocheng@google.com> | 2012-10-31 15:18:29 -0700 |
|---|---|---|
| committer | Chiao Cheng <chiaocheng@google.com> | 2012-10-31 15:18:29 -0700 |
| commit | a8feb7b88f2f67d8a80762dc54d336f1ea3a22d3 (patch) | |
| tree | 167ad6360502e0994ceeb97f252bf6999eec4a5c /src | |
| parent | cf2644a893c3dcb5eecbb55f518ff00f202702f9 (diff) | |
| download | android_packages_apps_ContactsCommon-a8feb7b88f2f67d8a80762dc54d336f1ea3a22d3.tar.gz android_packages_apps_ContactsCommon-a8feb7b88f2f67d8a80762dc54d336f1ea3a22d3.tar.bz2 android_packages_apps_ContactsCommon-a8feb7b88f2f67d8a80762dc54d336f1ea3a22d3.zip | |
Moving dependencies of PhoneFavoriteFragment.
Move common classes from Contacts to Contacts Common.
Bug: 6993891
Change-Id: Idd0b3115df810090340d1b10b04eb698d5facfb6
Diffstat (limited to 'src')
8 files changed, 802 insertions, 0 deletions
diff --git a/src/com/android/contacts/common/ContactPresenceIconUtil.java b/src/com/android/contacts/common/ContactPresenceIconUtil.java new file mode 100644 index 00000000..2f4c9eeb --- /dev/null +++ b/src/com/android/contacts/common/ContactPresenceIconUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.contacts.common; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.provider.ContactsContract.StatusUpdates; + +/** + * Define the contact present show policy in Contacts + */ +public class ContactPresenceIconUtil { + /** + * Get the presence icon resource according the status. + * + * @return null means don't show the status icon. + */ + public static Drawable getPresenceIcon (Context context, int status) { + // We don't show the offline status in Contacts + switch(status) { + case StatusUpdates.AVAILABLE: + case StatusUpdates.IDLE: + case StatusUpdates.AWAY: + case StatusUpdates.DO_NOT_DISTURB: + case StatusUpdates.INVISIBLE: + return context.getResources().getDrawable( + StatusUpdates.getPresenceIconResourceId(status)); + case StatusUpdates.OFFLINE: + // The undefined status is treated as OFFLINE in getPresenceIconResourceId(); + default: + return null; + } + } +} diff --git a/src/com/android/contacts/common/ContactStatusUtil.java b/src/com/android/contacts/common/ContactStatusUtil.java new file mode 100644 index 00000000..a7d19251 --- /dev/null +++ b/src/com/android/contacts/common/ContactStatusUtil.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.contacts.common; + +import android.content.Context; +import android.content.res.Resources; +import android.provider.ContactsContract.StatusUpdates; + +/** + * Provides static function to get default contact status message. + */ +public class ContactStatusUtil { + + private static final String TAG = "ContactStatusUtil"; + + public static String getStatusString(Context context, int presence) { + Resources resources = context.getResources(); + switch (presence) { + case StatusUpdates.AVAILABLE: + return resources.getString(R.string.status_available); + case StatusUpdates.IDLE: + case StatusUpdates.AWAY: + return resources.getString(R.string.status_away); + case StatusUpdates.DO_NOT_DISTURB: + return resources.getString(R.string.status_busy); + case StatusUpdates.OFFLINE: + case StatusUpdates.INVISIBLE: + default: + return null; + } + } + +} diff --git a/src/com/android/contacts/common/ContactTileLoaderFactory.java b/src/com/android/contacts/common/ContactTileLoaderFactory.java new file mode 100644 index 00000000..068aed8b --- /dev/null +++ b/src/com/android/contacts/common/ContactTileLoaderFactory.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ +package com.android.contacts.common; + +import android.content.Context; +import android.content.CursorLoader; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; + +/** + * Used to create {@link CursorLoader}s to load different groups of + * {@link com.android.contacts.list.ContactTileView}. + */ +public final class ContactTileLoaderFactory { + + public final static int CONTACT_ID = 0; + public final static int DISPLAY_NAME = 1; + public final static int STARRED = 2; + public final static int PHOTO_URI = 3; + public final static int LOOKUP_KEY = 4; + public final static int CONTACT_PRESENCE = 5; + public final static int CONTACT_STATUS = 6; + + // Only used for StrequentPhoneOnlyLoader + public final static int PHONE_NUMBER = 5; + public final static int PHONE_NUMBER_TYPE = 6; + public final static int PHONE_NUMBER_LABEL = 7; + + private static final String[] COLUMNS = new String[] { + Contacts._ID, // ..........................................0 + Contacts.DISPLAY_NAME, // .................................1 + Contacts.STARRED, // ......................................2 + Contacts.PHOTO_URI, // ....................................3 + Contacts.LOOKUP_KEY, // ...................................4 + Contacts.CONTACT_PRESENCE, // .............................5 + Contacts.CONTACT_STATUS, // ...............................6 + }; + + /** + * Projection used for the {@link Contacts#CONTENT_STREQUENT_URI} + * query when {@link ContactsContract#STREQUENT_PHONE_ONLY} flag + * is set to true. The main difference is the lack of presence + * and status data and the addition of phone number and label. + */ + private static final String[] COLUMNS_PHONE_ONLY = new String[] { + Contacts._ID, // ..........................................0 + Contacts.DISPLAY_NAME, // .................................1 + Contacts.STARRED, // ......................................2 + Contacts.PHOTO_URI, // ....................................3 + Contacts.LOOKUP_KEY, // ...................................4 + Phone.NUMBER, // ..........................................5 + Phone.TYPE, // ............................................6 + Phone.LABEL // ............................................7 + }; + + public static CursorLoader createStrequentLoader(Context context) { + return new CursorLoader(context, Contacts.CONTENT_STREQUENT_URI, COLUMNS, null, null, null); + } + + public static CursorLoader createStrequentPhoneOnlyLoader(Context context) { + Uri uri = Contacts.CONTENT_STREQUENT_URI.buildUpon() + .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true").build(); + + return new CursorLoader(context, uri, COLUMNS_PHONE_ONLY, null, null, null); + } + + public static CursorLoader createStarredLoader(Context context) { + return new CursorLoader(context, Contacts.CONTENT_URI, COLUMNS, + Contacts.STARRED + "=?", new String[]{"1"}, Contacts.DISPLAY_NAME + " ASC"); + } + + public static CursorLoader createFrequentLoader(Context context) { + return new CursorLoader(context, Contacts.CONTENT_FREQUENT_URI, COLUMNS, + Contacts.STARRED + "=?", new String[]{"0"}, null); + } +} diff --git a/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java b/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java new file mode 100644 index 00000000..2cfd36e7 --- /dev/null +++ b/src/com/android/contacts/common/dialog/ClearFrequentsDialog.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.contacts.common.dialog; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.content.ContentResolver; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.os.AsyncTask; +import android.os.Bundle; +import android.provider.ContactsContract; + +import com.android.contacts.common.R; + +/** + * Dialog that clears the frequently contacted list after confirming with the user. + */ +public class ClearFrequentsDialog extends DialogFragment { + /** Preferred way to show this dialog */ + public static void show(FragmentManager fragmentManager) { + ClearFrequentsDialog dialog = new ClearFrequentsDialog(); + dialog.show(fragmentManager, "clearFrequents"); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final ContentResolver resolver = getActivity().getContentResolver(); + final OnClickListener okListener = new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + final IndeterminateProgressDialog progressDialog = IndeterminateProgressDialog.show( + getFragmentManager(), getString(R.string.clearFrequentsProgress_title), + null, 500); + final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + resolver.delete(ContactsContract.DataUsageFeedback.DELETE_USAGE_URI, + null, null); + return null; + } + + @Override + protected void onPostExecute(Void result) { + progressDialog.dismiss(); + } + }; + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }; + return new AlertDialog.Builder(getActivity()) + .setTitle(R.string.clearFrequentsConfirmation_title) + .setMessage(R.string.clearFrequentsConfirmation) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, okListener) + .setCancelable(true) + .create(); + } +} diff --git a/src/com/android/contacts/common/dialog/IndeterminateProgressDialog.java b/src/com/android/contacts/common/dialog/IndeterminateProgressDialog.java new file mode 100644 index 00000000..2fe059f4 --- /dev/null +++ b/src/com/android/contacts/common/dialog/IndeterminateProgressDialog.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.contacts.common.dialog; + +import android.app.Dialog; +import android.app.DialogFragment; +import android.app.FragmentManager; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Handler; + +/** + * Indeterminate progress dialog wrapped up in a DialogFragment to work even when the device + * orientation is changed. Currently, only supports adding a title and/or message to the progress + * dialog. There is an additional parameter of the minimum amount of time to display the progress + * dialog even after a call to dismiss the dialog {@link #dismiss()} or + * {@link #dismissAllowingStateLoss()}. + * <p> + * To create and show the progress dialog, use + * {@link #show(FragmentManager, CharSequence, CharSequence, long)} and retain the reference to the + * IndeterminateProgressDialog instance. + * <p> + * To dismiss the dialog, use {@link #dismiss()} or {@link #dismissAllowingStateLoss()} on the + * instance. The instance returned by + * {@link #show(FragmentManager, CharSequence, CharSequence, long)} is guaranteed to be valid + * after a device orientation change because the {@link #setRetainInstance(boolean)} is called + * internally with true. + */ +public class IndeterminateProgressDialog extends DialogFragment { + private static final String TAG = IndeterminateProgressDialog.class.getSimpleName(); + + private CharSequence mTitle; + private CharSequence mMessage; + private long mMinDisplayTime; + private long mShowTime = 0; + private boolean mActivityReady = false; + private Dialog mOldDialog; + private final Handler mHandler = new Handler(); + private boolean mCalledSuperDismiss = false; + private boolean mAllowStateLoss; + private final Runnable mDismisser = new Runnable() { + @Override + public void run() { + superDismiss(); + } + }; + + /** + * Creates and shows an indeterminate progress dialog. Once the progress dialog is shown, it + * will be shown for at least the minDisplayTime (in milliseconds), so that the progress dialog + * does not flash in and out to quickly. + */ + public static IndeterminateProgressDialog show(FragmentManager fragmentManager, + CharSequence title, CharSequence message, long minDisplayTime) { + IndeterminateProgressDialog dialogFragment = new IndeterminateProgressDialog(); + dialogFragment.mTitle = title; + dialogFragment.mMessage = message; + dialogFragment.mMinDisplayTime = minDisplayTime; + dialogFragment.show(fragmentManager, TAG); + dialogFragment.mShowTime = System.currentTimeMillis(); + dialogFragment.setCancelable(false); + + return dialogFragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Create the progress dialog and set its properties + final ProgressDialog dialog = new ProgressDialog(getActivity()); + dialog.setIndeterminate(true); + dialog.setIndeterminateDrawable(null); + dialog.setTitle(mTitle); + dialog.setMessage(mMessage); + + return dialog; + } + + @Override + public void onStart() { + super.onStart(); + mActivityReady = true; + + // Check if superDismiss() had been called before. This can happen if in a long + // running operation, the user hits the home button and closes this fragment's activity. + // Upon returning, we want to dismiss this progress dialog fragment. + if (mCalledSuperDismiss) { + superDismiss(); + } + } + + @Override + public void onStop() { + super.onStop(); + mActivityReady = false; + } + + /** + * There is a race condition that is not handled properly by the DialogFragment class. + * If we don't check that this onDismiss callback isn't for the old progress dialog from before + * the device orientation change, then this will cause the newly created dialog after the + * orientation change to be dismissed immediately. + */ + @Override + public void onDismiss(DialogInterface dialog) { + if (mOldDialog != null && mOldDialog == dialog) { + // This is the callback from the old progress dialog that was already dismissed before + // the device orientation change, so just ignore it. + return; + } + super.onDismiss(dialog); + } + + /** + * Save the old dialog that is about to get destroyed in case this is due to a change + * in device orientation. This will allow us to intercept the callback to + * {@link #onDismiss(DialogInterface)} in case the callback happens after a new progress dialog + * instance was created. + */ + @Override + public void onDestroyView() { + mOldDialog = getDialog(); + super.onDestroyView(); + } + + /** + * This tells the progress dialog to dismiss itself after guaranteeing to be shown for the + * specified time in {@link #show(FragmentManager, CharSequence, CharSequence, long)}. + */ + @Override + public void dismiss() { + mAllowStateLoss = false; + dismissWhenReady(); + } + + /** + * This tells the progress dialog to dismiss itself (with state loss) after guaranteeing to be + * shown for the specified time in + * {@link #show(FragmentManager, CharSequence, CharSequence, long)}. + */ + @Override + public void dismissAllowingStateLoss() { + mAllowStateLoss = true; + dismissWhenReady(); + } + + /** + * Tells the progress dialog to dismiss itself after guaranteeing that the dialog had been + * showing for at least the minimum display time as set in + * {@link #show(FragmentManager, CharSequence, CharSequence, long)}. + */ + private void dismissWhenReady() { + // Compute how long the dialog has been showing + final long shownTime = System.currentTimeMillis() - mShowTime; + if (shownTime >= mMinDisplayTime) { + // dismiss immediately + mHandler.post(mDismisser); + } else { + // Need to wait some more, so compute the amount of time to sleep. + final long sleepTime = mMinDisplayTime - shownTime; + mHandler.postDelayed(mDismisser, sleepTime); + } + } + + /** + * Actually dismiss the dialog fragment. + */ + private void superDismiss() { + mCalledSuperDismiss = true; + if (mActivityReady) { + // The fragment is either in onStart or past it, but has not gotten to onStop yet. + // It is safe to dismiss this dialog fragment. + if (mAllowStateLoss) { + super.dismissAllowingStateLoss(); + } else { + super.dismiss(); + } + } + // If mActivityReady is false, then this dialog fragment has already passed the onStop + // state. This can happen if the user hit the 'home' button before this dialog fragment was + // dismissed or if there is a configuration change. + // In the event that this dialog fragment is re-attached and reaches onStart (e.g., + // because the user returns to this fragment's activity or the device configuration change + // has re-attached this dialog fragment), because the mCalledSuperDismiss flag was set to + // true, this dialog fragment will be dismissed within onStart. So, there's nothing else + // that needs to be done. + } +} diff --git a/src/com/android/contacts/common/format/FormatUtils.java b/src/com/android/contacts/common/format/FormatUtils.java new file mode 100644 index 00000000..6a274de8 --- /dev/null +++ b/src/com/android/contacts/common/format/FormatUtils.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.contacts.common.format; + +import android.database.CharArrayBuffer; +import android.graphics.Typeface; +import android.text.SpannableString; +import android.text.style.StyleSpan; + +import com.google.common.annotations.VisibleForTesting; + +import java.util.Arrays; + +/** + * Assorted utility methods related to text formatting in Contacts. + */ +public class FormatUtils { + + /** + * Finds the earliest point in buffer1 at which the first part of buffer2 matches. For example, + * overlapPoint("abcd", "cdef") == 2. + */ + public static int overlapPoint(CharArrayBuffer buffer1, CharArrayBuffer buffer2) { + if (buffer1 == null || buffer2 == null) { + return -1; + } + return overlapPoint(Arrays.copyOfRange(buffer1.data, 0, buffer1.sizeCopied), + Arrays.copyOfRange(buffer2.data, 0, buffer2.sizeCopied)); + } + + /** + * Finds the earliest point in string1 at which the first part of string2 matches. For example, + * overlapPoint("abcd", "cdef") == 2. + */ + @VisibleForTesting + public static int overlapPoint(String string1, String string2) { + if (string1 == null || string2 == null) { + return -1; + } + return overlapPoint(string1.toCharArray(), string2.toCharArray()); + } + + /** + * Finds the earliest point in array1 at which the first part of array2 matches. For example, + * overlapPoint("abcd", "cdef") == 2. + */ + public static int overlapPoint(char[] array1, char[] array2) { + if (array1 == null || array2 == null) { + return -1; + } + int count1 = array1.length; + int count2 = array2.length; + + // Ignore matching tails of the two arrays. + while (count1 > 0 && count2 > 0 && array1[count1 - 1] == array2[count2 - 1]) { + count1--; + count2--; + } + + int size = count2; + for (int i = 0; i < count1; i++) { + if (i + size > count1) { + size = count1 - i; + } + int j; + for (j = 0; j < size; j++) { + if (array1[i+j] != array2[j]) { + break; + } + } + if (j == size) { + return i; + } + } + + return -1; + } + + /** + * Applies the given style to a range of the input CharSequence. + * @param style The style to apply (see the style constants in {@link Typeface}). + * @param input The CharSequence to style. + * @param start Starting index of the range to style (will be clamped to be a minimum of 0). + * @param end Ending index of the range to style (will be clamped to a maximum of the input + * length). + * @param flags Bitmask for configuring behavior of the span. See {@link android.text.Spanned}. + * @return The styled CharSequence. + */ + public static CharSequence applyStyleToSpan(int style, CharSequence input, int start, int end, + int flags) { + // Enforce bounds of the char sequence. + start = Math.max(0, start); + end = Math.min(input.length(), end); + SpannableString text = new SpannableString(input); + text.setSpan(new StyleSpan(style), start, end, flags); + return text; + } + + @VisibleForTesting + public static void copyToCharArrayBuffer(String text, CharArrayBuffer buffer) { + if (text != null) { + char[] data = buffer.data; + if (data == null || data.length < text.length()) { + buffer.data = text.toCharArray(); + } else { + text.getChars(0, text.length(), data, 0); + } + buffer.sizeCopied = text.length(); + } else { + buffer.sizeCopied = 0; + } + } + + /** Returns a String that represents the content of the given {@link CharArrayBuffer}. */ + @VisibleForTesting + public static String charArrayBufferToString(CharArrayBuffer buffer) { + return new String(buffer.data, 0, buffer.sizeCopied); + } + + /** + * Finds the index of the first word that starts with the given prefix. + * <p> + * If not found, returns -1. + * + * @param text the text in which to search for the prefix + * @param prefix the text to find, in upper case letters + */ + public static int indexOfWordPrefix(CharSequence text, char[] prefix) { + if (prefix == null || text == null) { + return -1; + } + + int textLength = text.length(); + int prefixLength = prefix.length; + + if (prefixLength == 0 || textLength < prefixLength) { + return -1; + } + + int i = 0; + while (i < textLength) { + // Skip non-word characters + while (i < textLength && !Character.isLetterOrDigit(text.charAt(i))) { + i++; + } + + if (i + prefixLength > textLength) { + return -1; + } + + // Compare the prefixes + int j; + for (j = 0; j < prefixLength; j++) { + if (Character.toUpperCase(text.charAt(i + j)) != prefix[j]) { + break; + } + } + if (j == prefixLength) { + return i; + } + + // Skip this word + while (i < textLength && Character.isLetterOrDigit(text.charAt(i))) { + i++; + } + } + + return -1; + } + +} diff --git a/src/com/android/contacts/common/format/PrefixHighlighter.java b/src/com/android/contacts/common/format/PrefixHighlighter.java new file mode 100644 index 00000000..65edf587 --- /dev/null +++ b/src/com/android/contacts/common/format/PrefixHighlighter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.contacts.common.format; + +import android.text.SpannableString; +import android.text.style.ForegroundColorSpan; +import android.widget.TextView; + +/** + * Highlights the text in a text field. + */ +public class PrefixHighlighter { + private final int mPrefixHighlightColor; + + private ForegroundColorSpan mPrefixColorSpan; + + public PrefixHighlighter(int prefixHighlightColor) { + mPrefixHighlightColor = prefixHighlightColor; + } + + /** + * Sets the text on the given text view, highlighting the word that matches the given prefix. + * + * @param view the view on which to set the text + * @param text the string to use as the text + * @param prefix the prefix to look for + */ + public void setText(TextView view, String text, char[] prefix) { + view.setText(apply(text, prefix)); + } + + /** + * Returns a CharSequence which highlights the given prefix if found in the given text. + * + * @param text the text to which to apply the highlight + * @param prefix the prefix to look for + */ + public CharSequence apply(CharSequence text, char[] prefix) { + int index = FormatUtils.indexOfWordPrefix(text, prefix); + if (index != -1) { + if (mPrefixColorSpan == null) { + mPrefixColorSpan = new ForegroundColorSpan(mPrefixHighlightColor); + } + + SpannableString result = new SpannableString(text); + result.setSpan(mPrefixColorSpan, index, index + prefix.length, 0 /* flags */); + return result; + } else { + return text; + } + } +} diff --git a/src/com/android/contacts/common/format/SpannedTestUtils.java b/src/com/android/contacts/common/format/SpannedTestUtils.java new file mode 100644 index 00000000..8c2a22d0 --- /dev/null +++ b/src/com/android/contacts/common/format/SpannedTestUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.contacts.common.format; + +import android.test.suitebuilder.annotation.SmallTest; +import android.text.Html; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; +import android.widget.TextView; + +import junit.framework.Assert; + +/** + * Utility class to check the value of spanned text in text views. + */ +@SmallTest +public class SpannedTestUtils { + /** + * Checks that the text contained in the text view matches the given HTML text. + * + * @param expectedHtmlText the expected text to be in the text view + * @param textView the text view from which to get the text + */ + public static void checkHtmlText(String expectedHtmlText, TextView textView) { + String actualHtmlText = Html.toHtml((Spanned) textView.getText()); + if (TextUtils.isEmpty(expectedHtmlText)) { + // If the text is empty, it does not add the <p></p> bits to it. + Assert.assertEquals("", actualHtmlText); + } else { + Assert.assertEquals("<p dir=ltr>" + expectedHtmlText + "</p>\n", actualHtmlText); + } + } + + + /** + * Assert span exists in the correct location. + * + * @param seq The spannable string to check. + * @param start The starting index. + * @param end The ending index. + */ + public static void assertPrefixSpan(CharSequence seq, int start, int end) { + Assert.assertTrue(seq instanceof Spanned); + Spanned spannable = (Spanned) seq; + + if (start > 0) { + Assert.assertEquals(0, getNumForegroundColorSpansBetween(spannable, 0, start - 1)); + } + Assert.assertEquals(1, getNumForegroundColorSpansBetween(spannable, start, end)); + Assert.assertEquals(0, getNumForegroundColorSpansBetween(spannable, end + 1, + spannable.length() - 1)); + } + + private static int getNumForegroundColorSpansBetween(Spanned value, int start, int end) { + return value.getSpans(start, end, ForegroundColorSpan.class).length; + } + + /** + * Asserts that the given character sequence is not a Spanned object and text is correct. + * + * @param seq The sequence to check. + * @param expected The expected text. + */ + public static void assertNotSpanned(CharSequence seq, String expected) { + Assert.assertFalse(seq instanceof Spanned); + Assert.assertEquals(expected, seq); + } +} |
