blob: 95ac392039dfdb5f1e354e0ea108968698461717 [file] [log] [blame]
/*
* 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.list;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.SelectionBoundsAdjuster;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.QuickContactBadge;
import android.widget.TextView;
import com.android.contacts.common.ContactPresenceIconUtil;
import com.android.contacts.common.ContactStatusUtil;
import com.android.contacts.common.R;
import com.android.contacts.common.format.TextHighlighter;
import com.android.contacts.common.util.SearchUtil;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A custom view for an item in the contact list.
* The view contains the contact's photo, a set of text views (for name, status, etc...) and
* icons for presence and call.
* The view uses no XML file for layout and all the measurements and layouts are done
* in the onMeasure and onLayout methods.
*
* The layout puts the contact's photo on the right side of the view, the call icon (if present)
* to the left of the photo, the text lines are aligned to the left and the presence icon (if
* present) is set to the left of the status line.
*
* The layout also supports a header (used as a header of a group of contacts) that is above the
* contact's data and a divider between contact view.
*/
public class ContactListItemView extends ViewGroup
implements SelectionBoundsAdjuster {
// Style values for layout and appearance
// The initialized values are defaults if none is provided through xml.
private int mPreferredHeight = 0;
private int mGapBetweenImageAndText = 0;
private int mGapBetweenLabelAndData = 0;
private int mPresenceIconMargin = 4;
private int mPresenceIconSize = 16;
private int mHeaderTextColor = Color.BLACK;
private int mHeaderTextIndent = 0;
private int mHeaderTextSize = 12;
private int mHeaderUnderlineHeight = 1;
private int mHeaderUnderlineColor = 0;
private int mCountViewTextSize = 12;
private int mContactsCountTextColor = Color.BLACK;
private int mTextIndent = 0;
private Drawable mActivatedBackgroundDrawable;
/**
* Used with {@link #mLabelView}, specifying the width ratio between label and data.
*/
private int mLabelViewWidthWeight = 3;
/**
* Used with {@link #mDataView}, specifying the width ratio between label and data.
*/
private int mDataViewWidthWeight = 5;
// Will be used with adjustListItemSelectionBounds().
private int mSelectionBoundsMarginLeft;
private int mSelectionBoundsMarginRight;
// Horizontal divider between contact views.
private boolean mHorizontalDividerVisible = true;
private Drawable mHorizontalDividerDrawable;
private int mHorizontalDividerHeight;
protected static class HighlightSequence {
private final int start;
private final int end;
HighlightSequence(int start, int end) {
this.start = start;
this.end = end;
}
}
private ArrayList<HighlightSequence> mNameHighlightSequence;
private ArrayList<HighlightSequence> mNumberHighlightSequence;
// Highlighting prefix for names.
private String mHighlightedPrefix;
/**
* Where to put contact photo. This affects the other Views' layout or look-and-feel.
*
* TODO: replace enum with int constants
*/
public enum PhotoPosition {
LEFT,
RIGHT
}
static public final PhotoPosition getDefaultPhotoPosition(boolean opposite) {
final Locale locale = Locale.getDefault();
final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
switch (layoutDirection) {
case View.LAYOUT_DIRECTION_RTL:
return (opposite ? PhotoPosition.RIGHT : PhotoPosition.LEFT);
case View.LAYOUT_DIRECTION_LTR:
default:
return (opposite ? PhotoPosition.LEFT : PhotoPosition.RIGHT);
}
}
private PhotoPosition mPhotoPosition = getDefaultPhotoPosition(false /* normal/non opposite */);
// Header layout data
private boolean mHeaderVisible;
private View mHeaderDivider;
private int mHeaderBackgroundHeight = 30;
private TextView mHeaderTextView;
// The views inside the contact view
private boolean mQuickContactEnabled = true;
private QuickContactBadge mQuickContact;
private ImageView mPhotoView;
private TextView mNameTextView;
private TextView mPhoneticNameTextView;
private TextView mLabelView;
private TextView mDataView;
private TextView mSnippetView;
private TextView mStatusView;
private TextView mCountView;
private ImageView mPresenceIcon;
private ColorStateList mSecondaryTextColor;
private int mDefaultPhotoViewSize = 0;
/**
* Can be effective even when {@link #mPhotoView} is null, as we want to have horizontal padding
* to align other data in this View.
*/
private int mPhotoViewWidth;
/**
* Can be effective even when {@link #mPhotoView} is null, as we want to have vertical padding.
*/
private int mPhotoViewHeight;
/**
* Only effective when {@link #mPhotoView} is null.
* When true all the Views on the right side of the photo should have horizontal padding on
* those left assuming there is a photo.
*/
private boolean mKeepHorizontalPaddingForPhotoView;
/**
* Only effective when {@link #mPhotoView} is null.
*/
private boolean mKeepVerticalPaddingForPhotoView;
/**
* True when {@link #mPhotoViewWidth} and {@link #mPhotoViewHeight} are ready for being used.
* False indicates those values should be updated before being used in position calculation.
*/
private boolean mPhotoViewWidthAndHeightAreReady = false;
private int mNameTextViewHeight;
private int mPhoneticNameTextViewHeight;
private int mLabelViewHeight;
private int mDataViewHeight;
private int mSnippetTextViewHeight;
private int mStatusTextViewHeight;
// Holds Math.max(mLabelTextViewHeight, mDataViewHeight), assuming Label and Data share the
// same row.
private int mLabelAndDataViewMaxHeight;
// TODO: some TextView fields are using CharArrayBuffer while some are not. Determine which is
// more efficient for each case or in general, and simplify the whole implementation.
// Note: if we're sure MARQUEE will be used every time, there's no reason to use
// CharArrayBuffer, since MARQUEE requires Span and thus we need to copy characters inside the
// buffer to Spannable once, while CharArrayBuffer is for directly applying char array to
// TextView without any modification.
private final CharArrayBuffer mDataBuffer = new CharArrayBuffer(128);
private final CharArrayBuffer mPhoneticNameBuffer = new CharArrayBuffer(128);
private boolean mActivatedStateSupported;
private Rect mBoundsWithoutHeader = new Rect();
/** A helper used to highlight a prefix in a text field. */
private final TextHighlighter mTextHighlighter;
private CharSequence mUnknownNameText;
public ContactListItemView(Context context) {
super(context);
mContext = context;
mTextHighlighter = new TextHighlighter(Typeface.BOLD);
}
public ContactListItemView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
// Read all style values
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
mPreferredHeight = a.getDimensionPixelSize(
R.styleable.ContactListItemView_list_item_height, mPreferredHeight);
mActivatedBackgroundDrawable = a.getDrawable(
R.styleable.ContactListItemView_activated_background);
mHorizontalDividerDrawable = a.getDrawable(
R.styleable.ContactListItemView_list_item_divider);
mGapBetweenImageAndText = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_gap_between_image_and_text,
mGapBetweenImageAndText);
mGapBetweenLabelAndData = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_gap_between_label_and_data,
mGapBetweenLabelAndData);
mPresenceIconMargin = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_presence_icon_margin,
mPresenceIconMargin);
mPresenceIconSize = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_presence_icon_size, mPresenceIconSize);
mDefaultPhotoViewSize = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_photo_size, mDefaultPhotoViewSize);
mHeaderTextIndent = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_header_text_indent, mHeaderTextIndent);
mHeaderTextColor = a.getColor(
R.styleable.ContactListItemView_list_item_header_text_color, mHeaderTextColor);
mHeaderTextSize = a.getDimensionPixelSize(
R.styleable.ContactListItemView_list_item_header_text_size, mHeaderTextSize);
mHeaderBackgroundHeight = a.getDimensionPixelSize(
R.styleable.ContactListItemView_list_item_header_height, mHeaderBackgroundHeight);
mHeaderUnderlineHeight = a.getDimensionPixelSize(
R.styleable.ContactListItemView_list_item_header_underline_height,
mHeaderUnderlineHeight);
mHeaderUnderlineColor = a.getColor(
R.styleable.ContactListItemView_list_item_header_underline_color,
mHeaderUnderlineColor);
mTextIndent = a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_text_indent, mTextIndent);
mCountViewTextSize = a.getDimensionPixelSize(
R.styleable.ContactListItemView_list_item_contacts_count_text_size,
mCountViewTextSize);
mContactsCountTextColor = a.getColor(
R.styleable.ContactListItemView_list_item_contacts_count_text_color,
mContactsCountTextColor);
mDataViewWidthWeight = a.getInteger(
R.styleable.ContactListItemView_list_item_data_width_weight, mDataViewWidthWeight);
mLabelViewWidthWeight = a.getInteger(
R.styleable.ContactListItemView_list_item_label_width_weight,
mLabelViewWidthWeight);
setPaddingRelative(
a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_left, 0),
a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_top, 0),
a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_right, 0),
a.getDimensionPixelOffset(
R.styleable.ContactListItemView_list_item_padding_bottom, 0));
mTextHighlighter = new TextHighlighter(Typeface.BOLD);
a.recycle();
a = getContext().obtainStyledAttributes(android.R.styleable.Theme);
mSecondaryTextColor = a.getColorStateList(android.R.styleable.Theme_textColorSecondary);
a.recycle();
mHorizontalDividerHeight = mHorizontalDividerDrawable.getIntrinsicHeight();
if (mActivatedBackgroundDrawable != null) {
mActivatedBackgroundDrawable.setCallback(this);
}
mNameHighlightSequence = new ArrayList<HighlightSequence>();
mNumberHighlightSequence = new ArrayList<HighlightSequence>();
}
public void setUnknownNameText(CharSequence unknownNameText) {
mUnknownNameText = unknownNameText;
}
public void setQuickContactEnabled(boolean flag) {
mQuickContactEnabled = flag;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We will match parent's width and wrap content vertically, but make sure
// height is no less than listPreferredItemHeight.
final int specWidth = resolveSize(0, widthMeasureSpec);
final int preferredHeight;
if (mHorizontalDividerVisible) {
preferredHeight = mPreferredHeight + mHorizontalDividerHeight;
} else {
preferredHeight = mPreferredHeight;
}
mNameTextViewHeight = 0;
mPhoneticNameTextViewHeight = 0;
mLabelViewHeight = 0;
mDataViewHeight = 0;
mLabelAndDataViewMaxHeight = 0;
mSnippetTextViewHeight = 0;
mStatusTextViewHeight = 0;
ensurePhotoViewSize();
// Width each TextView is able to use.
final int effectiveWidth;
// All the other Views will honor the photo, so available width for them may be shrunk.
if (mPhotoViewWidth > 0 || mKeepHorizontalPaddingForPhotoView) {
effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight()
- (mPhotoViewWidth + mGapBetweenImageAndText);
} else {
effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight();
}
// Go over all visible text views and measure actual width of each of them.
// Also calculate their heights to get the total height for this entire view.
if (isVisible(mNameTextView)) {
// Caculate width for name text - this parallels similar measurement in onLayout.
int nameTextWidth = effectiveWidth;
if (mPhotoPosition != PhotoPosition.LEFT) {
nameTextWidth -= mTextIndent;
}
mNameTextView.measure(
MeasureSpec.makeMeasureSpec(nameTextWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mNameTextViewHeight = mNameTextView.getMeasuredHeight();
}
if (isVisible(mPhoneticNameTextView)) {
mPhoneticNameTextView.measure(
MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mPhoneticNameTextViewHeight = mPhoneticNameTextView.getMeasuredHeight();
}
// If both data (phone number/email address) and label (type like "MOBILE") are quite long,
// we should ellipsize both using appropriate ratio.
final int dataWidth;
final int labelWidth;
if (isVisible(mDataView)) {
if (isVisible(mLabelView)) {
final int totalWidth = effectiveWidth - mGapBetweenLabelAndData;
dataWidth = ((totalWidth * mDataViewWidthWeight)
/ (mDataViewWidthWeight + mLabelViewWidthWeight));
labelWidth = ((totalWidth * mLabelViewWidthWeight) /
(mDataViewWidthWeight + mLabelViewWidthWeight));
} else {
dataWidth = effectiveWidth;
labelWidth = 0;
}
} else {
dataWidth = 0;
if (isVisible(mLabelView)) {
labelWidth = effectiveWidth;
} else {
labelWidth = 0;
}
}
if (isVisible(mDataView)) {
mDataView.measure(MeasureSpec.makeMeasureSpec(dataWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mDataViewHeight = mDataView.getMeasuredHeight();
}
if (isVisible(mLabelView)) {
// For performance reason we don't want AT_MOST usually, but when the picture is
// on right, we need to use it anyway because mDataView is next to mLabelView.
final int mode = (mPhotoPosition == PhotoPosition.LEFT
? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST);
mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, mode),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mLabelViewHeight = mLabelView.getMeasuredHeight();
}
mLabelAndDataViewMaxHeight = Math.max(mLabelViewHeight, mDataViewHeight);
if (isVisible(mSnippetView)) {
mSnippetView.measure(
MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mSnippetTextViewHeight = mSnippetView.getMeasuredHeight();
}
// Status view height is the biggest of the text view and the presence icon
if (isVisible(mPresenceIcon)) {
mPresenceIcon.measure(
MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY));
mStatusTextViewHeight = mPresenceIcon.getMeasuredHeight();
}
if (isVisible(mStatusView)) {
// Presence and status are in a same row, so status will be affected by icon size.
final int statusWidth;
if (isVisible(mPresenceIcon)) {
statusWidth = (effectiveWidth - mPresenceIcon.getMeasuredWidth()
- mPresenceIconMargin);
} else {
statusWidth = effectiveWidth;
}
mStatusView.measure(MeasureSpec.makeMeasureSpec(statusWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mStatusTextViewHeight =
Math.max(mStatusTextViewHeight, mStatusView.getMeasuredHeight());
}
// Calculate height including padding.
int height = (mNameTextViewHeight + mPhoneticNameTextViewHeight +
mLabelAndDataViewMaxHeight +
mSnippetTextViewHeight + mStatusTextViewHeight);
// Make sure the height is at least as high as the photo
height = Math.max(height, mPhotoViewHeight + getPaddingBottom() + getPaddingTop());
// Add horizontal divider height
if (mHorizontalDividerVisible) {
height += mHorizontalDividerHeight;
}
// Make sure height is at least the preferred height
height = Math.max(height, preferredHeight);
// Add the height of the header if visible
if (mHeaderVisible) {
final int headerWidth = specWidth -
getPaddingLeft() - getPaddingRight() - mHeaderTextIndent;
mHeaderTextView.measure(
MeasureSpec.makeMeasureSpec(headerWidth, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
if (mCountView != null) {
mCountView.measure(
MeasureSpec.makeMeasureSpec(headerWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
}
mHeaderBackgroundHeight = Math.max(mHeaderBackgroundHeight,
mHeaderTextView.getMeasuredHeight());
height += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
}
setMeasuredDimension(specWidth, height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int height = bottom - top;
final int width = right - left;
// Determine the vertical bounds by laying out the header first.
int topBound = 0;
int bottomBound = height;
int leftBound = getPaddingLeft();
int rightBound = width - getPaddingRight();
final boolean isLayoutRtl = isLayoutRtl();
// Put the header in the top of the contact view (Text + underline view)
if (mHeaderVisible) {
mHeaderTextView.layout(isLayoutRtl ? leftBound : leftBound + mHeaderTextIndent,
0,
isLayoutRtl ? rightBound - mHeaderTextIndent : rightBound,
mHeaderBackgroundHeight);
if (mCountView != null) {
mCountView.layout(rightBound - mCountView.getMeasuredWidth(),
0,
rightBound,
mHeaderBackgroundHeight);
}
mHeaderDivider.layout(leftBound,
mHeaderBackgroundHeight,
rightBound,
mHeaderBackgroundHeight + mHeaderUnderlineHeight);
topBound += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
}
// Put horizontal divider at the bottom
if (mHorizontalDividerVisible) {
mHorizontalDividerDrawable.setBounds(
leftBound,
height - mHorizontalDividerHeight,
rightBound,
height);
bottomBound -= mHorizontalDividerHeight;
}
mBoundsWithoutHeader.set(0, topBound, width, bottomBound);
if (mActivatedStateSupported && isActivated()) {
mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
}
final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
if (mPhotoPosition == PhotoPosition.LEFT) {
// Photo is the left most view. All the other Views should on the right of the photo.
if (photoView != null) {
// Center the photo vertically
final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
photoView.layout(
leftBound,
photoTop,
leftBound + mPhotoViewWidth,
photoTop + mPhotoViewHeight);
leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
} else if (mKeepHorizontalPaddingForPhotoView) {
// Draw nothing but keep the padding.
leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
}
} else {
// Photo is the right most view. Right bound should be adjusted that way.
if (photoView != null) {
// Center the photo vertically
final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2;
photoView.layout(
rightBound - mPhotoViewWidth,
photoTop,
rightBound,
photoTop + mPhotoViewHeight);
rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
} else if (mKeepHorizontalPaddingForPhotoView) {
// Draw nothing but keep the padding.
rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
}
// Add indent between left-most padding and texts.
leftBound += mTextIndent;
}
// Center text vertically
final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
mLabelAndDataViewMaxHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
// Layout all text view and presence icon
// Put name TextView first
if (isVisible(mNameTextView)) {
mNameTextView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mNameTextViewHeight);
textTopBound += mNameTextViewHeight;
}
// Presence and status
if (isLayoutRtl) {
int statusRightBound = rightBound;
if (isVisible(mPresenceIcon)) {
int iconWidth = mPresenceIcon.getMeasuredWidth();
mPresenceIcon.layout(
rightBound - iconWidth,
textTopBound,
rightBound,
textTopBound + mStatusTextViewHeight);
statusRightBound -= (iconWidth + mPresenceIconMargin);
}
if (isVisible(mStatusView)) {
mStatusView.layout(leftBound,
textTopBound,
statusRightBound,
textTopBound + mStatusTextViewHeight);
}
} else {
int statusLeftBound = leftBound;
if (isVisible(mPresenceIcon)) {
int iconWidth = mPresenceIcon.getMeasuredWidth();
mPresenceIcon.layout(
leftBound,
textTopBound,
leftBound + iconWidth,
textTopBound + mStatusTextViewHeight);
statusLeftBound += (iconWidth + mPresenceIconMargin);
}
if (isVisible(mStatusView)) {
mStatusView.layout(statusLeftBound,
textTopBound,
rightBound,
textTopBound + mStatusTextViewHeight);
}
}
if (isVisible(mStatusView) || isVisible(mPresenceIcon)) {
textTopBound += mStatusTextViewHeight;
}
// Rest of text views
int dataLeftBound = leftBound;
if (isVisible(mPhoneticNameTextView)) {
mPhoneticNameTextView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mPhoneticNameTextViewHeight);
textTopBound += mPhoneticNameTextViewHeight;
}
// Label and Data align bottom.
if (isVisible(mLabelView)) {
if (mPhotoPosition == PhotoPosition.LEFT) {
// When photo is on left, label is placed on the right edge of the list item.
mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
rightBound,
textTopBound + mLabelAndDataViewMaxHeight);
rightBound -= mLabelView.getMeasuredWidth();
} else {
// When photo is on right, label is placed on the left of data view.
dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
mLabelView.layout(leftBound,
textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
dataLeftBound,
textTopBound + mLabelAndDataViewMaxHeight);
dataLeftBound += mGapBetweenLabelAndData;
}
}
if (isVisible(mDataView)) {
mDataView.layout(dataLeftBound,
textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
rightBound,
textTopBound + mLabelAndDataViewMaxHeight);
}
if (isVisible(mLabelView) || isVisible(mDataView)) {
textTopBound += mLabelAndDataViewMaxHeight;
}
if (isVisible(mSnippetView)) {
mSnippetView.layout(leftBound,
textTopBound,
rightBound,
textTopBound + mSnippetTextViewHeight);
}
}
@Override
public void adjustListItemSelectionBounds(Rect bounds) {
bounds.top += mBoundsWithoutHeader.top;
bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
bounds.left += mSelectionBoundsMarginLeft;
bounds.right -= mSelectionBoundsMarginRight;
}
protected boolean isVisible(View view) {
return view != null && view.getVisibility() == View.VISIBLE;
}
/**
* Extracts width and height from the style
*/
private void ensurePhotoViewSize() {
if (!mPhotoViewWidthAndHeightAreReady) {
mPhotoViewWidth = mPhotoViewHeight = getDefaultPhotoViewSize();
if (!mQuickContactEnabled && mPhotoView == null) {
if (!mKeepHorizontalPaddingForPhotoView) {
mPhotoViewWidth = 0;
}
if (!mKeepVerticalPaddingForPhotoView) {
mPhotoViewHeight = 0;
}
}
mPhotoViewWidthAndHeightAreReady = true;
}
}
protected void setDefaultPhotoViewSize(int pixels) {
mDefaultPhotoViewSize = pixels;
}
protected int getDefaultPhotoViewSize() {
return mDefaultPhotoViewSize;
}
/**
* Gets a LayoutParam that corresponds to the default photo size.
*
* @return A new LayoutParam.
*/
private LayoutParams getDefaultPhotoLayoutParams() {
LayoutParams params = generateDefaultLayoutParams();
params.width = getDefaultPhotoViewSize();
params.height = params.width;
return params;
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mActivatedStateSupported) {
mActivatedBackgroundDrawable.setState(getDrawableState());
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return who == mActivatedBackgroundDrawable || super.verifyDrawable(who);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mActivatedStateSupported) {
mActivatedBackgroundDrawable.jumpToCurrentState();
}
}
@Override
public void dispatchDraw(Canvas canvas) {
if (mActivatedStateSupported && isActivated()) {
mActivatedBackgroundDrawable.draw(canvas);
}
if (mHorizontalDividerVisible) {
mHorizontalDividerDrawable.draw(canvas);
}
super.dispatchDraw(canvas);
}
/**
* Sets the flag that determines whether a divider should drawn at the bottom
* of the view.
*/
public void setDividerVisible(boolean visible) {
mHorizontalDividerVisible = visible;
}
/**
* Sets section header or makes it invisible if the title is null.
*/
public void setSectionHeader(String title) {
if (!TextUtils.isEmpty(title)) {
if (mHeaderTextView == null) {
mHeaderTextView = new TextView(mContext);
mHeaderTextView.setTextColor(mHeaderTextColor);
mHeaderTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mHeaderTextSize);
mHeaderTextView.setTextAppearance(mContext, R.style.SectionHeaderStyle);
mHeaderTextView.setGravity(Gravity.CENTER_VERTICAL);
mHeaderTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
addView(mHeaderTextView);
}
if (mHeaderDivider == null) {
mHeaderDivider = new View(mContext);
mHeaderDivider.setBackgroundColor(mHeaderUnderlineColor);
addView(mHeaderDivider);
}
setMarqueeText(mHeaderTextView, title);
mHeaderTextView.setVisibility(View.VISIBLE);
mHeaderDivider.setVisibility(View.VISIBLE);
mHeaderTextView.setAllCaps(true);
mHeaderVisible = true;
} else {
if (mHeaderTextView != null) {
mHeaderTextView.setVisibility(View.GONE);
}
if (mHeaderDivider != null) {
mHeaderDivider.setVisibility(View.GONE);
}
mHeaderVisible = false;
}
}
/**
* Returns the quick contact badge, creating it if necessary.
*/
public QuickContactBadge getQuickContact() {
if (!mQuickContactEnabled) {
throw new IllegalStateException("QuickContact is disabled for this view");
}
if (mQuickContact == null) {
mQuickContact = new QuickContactBadge(mContext);
mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams());
if (mNameTextView != null) {
mQuickContact.setContentDescription(mContext.getString(
R.string.description_quick_contact_for, mNameTextView.getText()));
}
addView(mQuickContact);
mPhotoViewWidthAndHeightAreReady = false;
}
return mQuickContact;
}
/**
* Returns the photo view, creating it if necessary.
*/
public ImageView getPhotoView() {
if (mPhotoView == null) {
mPhotoView = new ImageView(mContext);
mPhotoView.setLayoutParams(getDefaultPhotoLayoutParams());
// Quick contact style used above will set a background - remove it
mPhotoView.setBackground(null);
addView(mPhotoView);
mPhotoViewWidthAndHeightAreReady = false;
}
return mPhotoView;
}
/**
* Removes the photo view.
*/
public void removePhotoView() {
removePhotoView(false, true);
}
/**
* Removes the photo view.
*
* @param keepHorizontalPadding True means data on the right side will have
* padding on left, pretending there is still a photo view.
* @param keepVerticalPadding True means the View will have some height
* enough for accommodating a photo view.
*/
public void removePhotoView(boolean keepHorizontalPadding, boolean keepVerticalPadding) {
mPhotoViewWidthAndHeightAreReady = false;
mKeepHorizontalPaddingForPhotoView = keepHorizontalPadding;
mKeepVerticalPaddingForPhotoView = keepVerticalPadding;
if (mPhotoView != null) {
removeView(mPhotoView);
mPhotoView = null;
}
if (mQuickContact != null) {
removeView(mQuickContact);
mQuickContact = null;
}
}
/**
* Sets a word prefix that will be highlighted if encountered in fields like
* name and search snippet. This will disable the mask highlighting for names.
* <p>
* NOTE: must be all upper-case
*/
public void setHighlightedPrefix(String upperCasePrefix) {
mHighlightedPrefix = upperCasePrefix;
}
/**
* Clears previously set highlight sequences for the view.
*/
public void clearHighlightSequences() {
mNameHighlightSequence.clear();
mNumberHighlightSequence.clear();
mHighlightedPrefix = null;
}
/**
* Adds a highlight sequence to the name highlighter.
* @param start The start position of the highlight sequence.
* @param end The end position of the highlight sequence.
*/
public void addNameHighlightSequence(int start, int end) {
mNameHighlightSequence.add(new HighlightSequence(start, end));
}
/**
* Adds a highlight sequence to the number highlighter.
* @param start The start position of the highlight sequence.
* @param end The end position of the highlight sequence.
*/
public void addNumberHighlightSequence(int start, int end) {
mNumberHighlightSequence.add(new HighlightSequence(start, end));
}
/**
* Returns the text view for the contact name, creating it if necessary.
*/
public TextView getNameTextView() {
if (mNameTextView == null) {
mNameTextView = new TextView(mContext);
mNameTextView.setSingleLine(true);
mNameTextView.setEllipsize(getTextEllipsis());
mNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
// Manually call setActivated() since this view may be added after the first
// setActivated() call toward this whole item view.
mNameTextView.setActivated(isActivated());
mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
mNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
mNameTextView.setId(R.id.cliv_name_textview);
addView(mNameTextView);
}
return mNameTextView;
}
/**
* Adds or updates a text view for the phonetic name.
*/
public void setPhoneticName(char[] text, int size) {
if (text == null || size == 0) {
if (mPhoneticNameTextView != null) {
mPhoneticNameTextView.setVisibility(View.GONE);
}
} else {
getPhoneticNameTextView();
setMarqueeText(mPhoneticNameTextView, text, size);
mPhoneticNameTextView.setVisibility(VISIBLE);
}
}
/**
* Returns the text view for the phonetic name, creating it if necessary.
*/
public TextView getPhoneticNameTextView() {
if (mPhoneticNameTextView == null) {
mPhoneticNameTextView = new TextView(mContext);
mPhoneticNameTextView.setSingleLine(true);
mPhoneticNameTextView.setEllipsize(getTextEllipsis());
mPhoneticNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
mPhoneticNameTextView.setActivated(isActivated());
mPhoneticNameTextView.setId(R.id.cliv_phoneticname_textview);
addView(mPhoneticNameTextView);
}
return mPhoneticNameTextView;
}
/**
* Adds or updates a text view for the data label.
*/
public void setLabel(CharSequence text) {
if (TextUtils.isEmpty(text)) {
if (mLabelView != null) {
mLabelView.setVisibility(View.GONE);
}
} else {
getLabelView();
setMarqueeText(mLabelView, text);
mLabelView.setVisibility(VISIBLE);
}
}
/**
* Returns the text view for the data label, creating it if necessary.
*/
public TextView getLabelView() {
if (mLabelView == null) {
mLabelView = new TextView(mContext);
mLabelView.setSingleLine(true);
mLabelView.setEllipsize(getTextEllipsis());
mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
if (mPhotoPosition == PhotoPosition.LEFT) {
mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mCountViewTextSize);
mLabelView.setAllCaps(true);
mLabelView.setGravity(Gravity.END);
} else {
mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
}
mLabelView.setActivated(isActivated());
mLabelView.setId(R.id.cliv_label_textview);
addView(mLabelView);
}
return mLabelView;
}
/**
* Adds or updates a text view for the data element.
*/
public void setData(char[] text, int size) {
if (text == null || size == 0) {
if (mDataView != null) {
mDataView.setVisibility(View.GONE);
}
} else {
getDataView();
setMarqueeText(mDataView, text, size);
mDataView.setVisibility(VISIBLE);
}
}
/**
* Sets phone number for a list item. This takes care of number highlighting if the highlight
* mask exists.
*/
public void setPhoneNumber(String text) {
if (text == null) {
if (mDataView != null) {
mDataView.setVisibility(View.GONE);
}
} else {
getDataView();
// Sets phone number texts for display after highlighting it, if applicable.
//CharSequence textToSet = text;
final SpannableString textToSet = new SpannableString(text);
if (mNumberHighlightSequence.size() != 0) {
final HighlightSequence highlightSequence = mNumberHighlightSequence.get(0);
mTextHighlighter.applyMaskingHighlight(textToSet, highlightSequence.start,
highlightSequence.end);
}
setMarqueeText(mDataView, textToSet);
mDataView.setVisibility(VISIBLE);
// We have a phone number as "mDataView" so make it always LTR and VIEW_START
mDataView.setTextDirection(View.TEXT_DIRECTION_LTR);
mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
}
}
private void setMarqueeText(TextView textView, char[] text, int size) {
if (getTextEllipsis() == TruncateAt.MARQUEE) {
setMarqueeText(textView, new String(text, 0, size));
} else {
textView.setText(text, 0, size);
}
}
private void setMarqueeText(TextView textView, CharSequence text) {
if (getTextEllipsis() == TruncateAt.MARQUEE) {
// To show MARQUEE correctly (with END effect during non-active state), we need
// to build Spanned with MARQUEE in addition to TextView's ellipsize setting.
final SpannableString spannable = new SpannableString(text);
spannable.setSpan(TruncateAt.MARQUEE, 0, spannable.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(spannable);
} else {
textView.setText(text);
}
}
/**
* Returns the text view for the data text, creating it if necessary.
*/
public TextView getDataView() {
if (mDataView == null) {
mDataView = new TextView(mContext);
mDataView.setSingleLine(true);
mDataView.setEllipsize(getTextEllipsis());
mDataView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
mDataView.setActivated(isActivated());
mDataView.setId(R.id.cliv_data_view);
addView(mDataView);
}
return mDataView;
}
/**
* Adds or updates a text view for the search snippet.
*/
public void setSnippet(String text) {
if (TextUtils.isEmpty(text)) {
if (mSnippetView != null) {
mSnippetView.setVisibility(View.GONE);
}
} else {
mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix);
mSnippetView.setVisibility(VISIBLE);
}
}
/**
* Returns the text view for the search snippet, creating it if necessary.
*/
public TextView getSnippetView() {
if (mSnippetView == null) {
mSnippetView = new TextView(mContext);
mSnippetView.setSingleLine(true);
mSnippetView.setEllipsize(getTextEllipsis());
mSnippetView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
mSnippetView.setTypeface(mSnippetView.getTypeface(), Typeface.BOLD);
mSnippetView.setActivated(isActivated());
addView(mSnippetView);
}
return mSnippetView;
}
/**
* Returns the text view for the status, creating it if necessary.
*/
public TextView getStatusView() {
if (mStatusView == null) {
mStatusView = new TextView(mContext);
mStatusView.setSingleLine(true);
mStatusView.setEllipsize(getTextEllipsis());
mStatusView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
mStatusView.setTextColor(mSecondaryTextColor);
mStatusView.setActivated(isActivated());
mStatusView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
addView(mStatusView);
}
return mStatusView;
}
/**
* Returns the text view for the contacts count, creating it if necessary.
*/
public TextView getCountView() {
if (mCountView == null) {
mCountView = new TextView(mContext);
mCountView.setSingleLine(true);
mCountView.setEllipsize(getTextEllipsis());
mCountView.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
mCountView.setTextColor(R.color.people_app_theme_color);
addView(mCountView);
}
return mCountView;
}
/**
* Adds or updates a text view for the contacts count.
*/
public void setCountView(CharSequence text) {
if (TextUtils.isEmpty(text)) {
if (mCountView != null) {
mCountView.setVisibility(View.GONE);
}
} else {
getCountView();
setMarqueeText(mCountView, text);
mCountView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCountViewTextSize);
mCountView.setGravity(Gravity.CENTER_VERTICAL);
mCountView.setTextColor(mContactsCountTextColor);
mCountView.setVisibility(VISIBLE);
}
}
/**
* Adds or updates a text view for the status.
*/
public void setStatus(CharSequence text) {
if (TextUtils.isEmpty(text)) {
if (mStatusView != null) {
mStatusView.setVisibility(View.GONE);
}
} else {
getStatusView();
setMarqueeText(mStatusView, text);
mStatusView.setVisibility(VISIBLE);
}
}
/**
* Adds or updates the presence icon view.
*/
public void setPresence(Drawable icon) {
if (icon != null) {
if (mPresenceIcon == null) {
mPresenceIcon = new ImageView(mContext);
addView(mPresenceIcon);
}
mPresenceIcon.setImageDrawable(icon);
mPresenceIcon.setScaleType(ScaleType.CENTER);
mPresenceIcon.setVisibility(View.VISIBLE);
} else {
if (mPresenceIcon != null) {
mPresenceIcon.setVisibility(View.GONE);
}
}
}
private TruncateAt getTextEllipsis() {
return TruncateAt.MARQUEE;
}
public void showDisplayName(Cursor cursor, int nameColumnIndex, int displayOrder) {
CharSequence name = cursor.getString(nameColumnIndex);
setDisplayName(name);
// Since the quick contact content description is derived from the display name and there is
// no guarantee that when the quick contact is initialized the display name is already set,
// do it here too.
if (mQuickContact != null) {
mQuickContact.setContentDescription(mContext.getString(
R.string.description_quick_contact_for, mNameTextView.getText()));
}
}
public void setDisplayName(CharSequence name, boolean highlight) {
if (!TextUtils.isEmpty(name) && highlight) {
clearHighlightSequences();
addNameHighlightSequence(0, name.length());
}
setDisplayName(name);
}
public void setDisplayName(CharSequence name) {
if (!TextUtils.isEmpty(name)) {
// Chooses the available highlighting method for highlighting.
if (mHighlightedPrefix != null) {
name = mTextHighlighter.applyPrefixHighlight(name, mHighlightedPrefix);
} else if (mNameHighlightSequence.size() != 0) {
final SpannableString spannableName = new SpannableString(name);
for (HighlightSequence highlightSequence : mNameHighlightSequence) {
mTextHighlighter.applyMaskingHighlight(spannableName, highlightSequence.start,
highlightSequence.end);
}
name = spannableName;
}
} else {
name = mUnknownNameText;
}
setMarqueeText(getNameTextView(), name);
}
public void hideDisplayName() {
if (mNameTextView != null) {
removeView(mNameTextView);
mNameTextView = null;
}
}
public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
cursor.copyStringToBuffer(phoneticNameColumnIndex, mPhoneticNameBuffer);
int phoneticNameSize = mPhoneticNameBuffer.sizeCopied;
if (phoneticNameSize != 0) {
setPhoneticName(mPhoneticNameBuffer.data, phoneticNameSize);
} else {
setPhoneticName(null, 0);
}
}
public void hidePhoneticName() {
if (mPhoneticNameTextView != null) {
removeView(mPhoneticNameTextView);
mPhoneticNameTextView = null;
}
}
/**
* Sets the proper icon (star or presence or nothing) and/or status message.
*/
public void showPresenceAndStatusMessage(Cursor cursor, int presenceColumnIndex,
int contactStatusColumnIndex) {
Drawable icon = null;
int presence = 0;
if (!cursor.isNull(presenceColumnIndex)) {
presence = cursor.getInt(presenceColumnIndex);
icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), presence);
}
setPresence(icon);
String statusMessage = null;
if (contactStatusColumnIndex != 0 && !cursor.isNull(contactStatusColumnIndex)) {
statusMessage = cursor.getString(contactStatusColumnIndex);
}
// If there is no status message from the contact, but there was a presence value, then use
// the default status message string
if (statusMessage == null && presence != 0) {
statusMessage = ContactStatusUtil.getStatusString(getContext(), presence);
}
setStatus(statusMessage);
}
/**
* Shows search snippet.
*/
public void showSnippet(Cursor cursor, int summarySnippetColumnIndex) {
if (cursor.getColumnCount() <= summarySnippetColumnIndex) {
setSnippet(null);
return;
}
String snippet = cursor.getString(summarySnippetColumnIndex);
// Do client side snippeting if provider didn't do it
final Bundle extras = cursor.getExtras();
if (extras.getBoolean(ContactsContract.DEFERRED_SNIPPETING)) {
final String query = extras.getString(ContactsContract.DEFERRED_SNIPPETING_QUERY);
String displayName = null;
int displayNameIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
if (displayNameIndex >= 0) {
displayName = cursor.getString(displayNameIndex);
}
snippet = updateSnippet(snippet, query, displayName);
} else {
if (snippet != null) {
int from = 0;
int to = snippet.length();
int start = snippet.indexOf(DefaultContactListAdapter.SNIPPET_START_MATCH);
if (start == -1) {
snippet = null;
} else {
int firstNl = snippet.lastIndexOf('\n', start);
if (firstNl != -1) {
from = firstNl + 1;
}
int end = snippet.lastIndexOf(DefaultContactListAdapter.SNIPPET_END_MATCH);
if (end != -1) {
int lastNl = snippet.indexOf('\n', end);
if (lastNl != -1) {
to = lastNl;
}
}
StringBuilder sb = new StringBuilder();
for (int i = from; i < to; i++) {
char c = snippet.charAt(i);
if (c != DefaultContactListAdapter.SNIPPET_START_MATCH &&
c != DefaultContactListAdapter.SNIPPET_END_MATCH) {
sb.append(c);
}
}
snippet = sb.toString();
}
}
}
setSnippet(snippet);
}
/**
* Used for deferred snippets from the database. The contents come back as large strings which
* need to be extracted for display.
*
* @param snippet The snippet from the database.
* @param query The search query substring.
* @param displayName The contact display name.
* @return The proper snippet to display.
*/
private String updateSnippet(String snippet, String query, String displayName) {
if (TextUtils.isEmpty(snippet) || TextUtils.isEmpty(query)) {
return null;
}
query = SearchUtil.cleanStartAndEndOfSearchQuery(query.toLowerCase());
// If the display name already contains the query term, return empty - snippets should
// not be needed in that case.
if (!TextUtils.isEmpty(displayName)) {
final String lowerDisplayName = displayName.toLowerCase();
final List<String> nameTokens = split(lowerDisplayName);
for (String nameToken : nameTokens) {
if (nameToken.startsWith(query)) {
return null;
}
}
}
// The snippet may contain multiple data lines.
// Show the first line that matches the query.
final SearchUtil.MatchedLine matched = SearchUtil.findMatchingLine(snippet, query);
if (matched != null && matched.line != null) {
// Tokenize for long strings since the match may be at the end of it.
// Skip this part for short strings since the whole string will be displayed.
// Most contact strings are short so the snippetize method will be called infrequently.
final int lengthThreshold = getResources().getInteger(
R.integer.snippet_length_before_tokenize);
if (matched.line.length() > lengthThreshold) {
return snippetize(matched.line, matched.startIndex, lengthThreshold);
} else {
return matched.line;
}
}
// No match found.
return null;
}
private String snippetize(String line, int matchIndex, int maxLength) {
// Show up to maxLength characters. But we only show full tokens so show the last full token
// up to maxLength characters. So as many starting tokens as possible before trying ending
// tokens.
int remainingLength = maxLength;
int tempRemainingLength = remainingLength;
// Start the end token after the matched query.
int index = matchIndex;
int endTokenIndex = index;
// Find the match token first.
while (index < line.length()) {
if (!Character.isLetterOrDigit(line.charAt(index))) {
endTokenIndex = index;
remainingLength = tempRemainingLength;
break;
}
tempRemainingLength--;
index++;
}
// Find as much content before the match.
index = matchIndex - 1;
tempRemainingLength = remainingLength;
int startTokenIndex = matchIndex;
while (index > -1 && tempRemainingLength > 0) {
if (!Character.isLetterOrDigit(line.charAt(index))) {
startTokenIndex = index;
remainingLength = tempRemainingLength;
}
tempRemainingLength--;
index--;
}
index = endTokenIndex;
tempRemainingLength = remainingLength;
// Find remaining content at after match.
while (index < line.length() && tempRemainingLength > 0) {
if (!Character.isLetterOrDigit(line.charAt(index))) {
endTokenIndex = index;
}
tempRemainingLength--;
index++;
}
// Append ellipse if there is content before or after.
final StringBuilder sb = new StringBuilder();
if (startTokenIndex > 0) {
sb.append("...");
}
sb.append(line.substring(startTokenIndex, endTokenIndex));
if (endTokenIndex < line.length()) {
sb.append("...");
}
return sb.toString();
}
private static final Pattern SPLIT_PATTERN = Pattern.compile(
"([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
/**
* Helper method for splitting a string into tokens. The lists passed in are populated with
* the
* tokens and offsets into the content of each token. The tokenization function parses e-mail
* addresses as a single token; otherwise it splits on any non-alphanumeric character.
*
* @param content Content to split.
* @return List of token strings.
*/
private static List<String> split(String content) {
final Matcher matcher = SPLIT_PATTERN.matcher(content);
final ArrayList<String> tokens = Lists.newArrayList();
while (matcher.find()) {
tokens.add(matcher.group());
}
return tokens;
}
/**
* Shows data element.
*/
public void showData(Cursor cursor, int dataColumnIndex) {
cursor.copyStringToBuffer(dataColumnIndex, mDataBuffer);
setData(mDataBuffer.data, mDataBuffer.sizeCopied);
}
public void showPhoneNumber(Cursor cursor, int dataColumnIndex) {
// Highlights the number and aligns text before showing.
setPhoneNumber(cursor.getString(dataColumnIndex));
}
public void setActivatedStateSupported(boolean flag) {
this.mActivatedStateSupported = flag;
}
@Override
public void requestLayout() {
// We will assume that once measured this will not need to resize
// itself, so there is no need to pass the layout request to the parent
// view (ListView).
forceLayout();
}
public void setPhotoPosition(PhotoPosition photoPosition) {
mPhotoPosition = photoPosition;
}
public PhotoPosition getPhotoPosition() {
return mPhotoPosition;
}
/**
* Specifies left and right margin for selection bounds. See also
* {@link #adjustListItemSelectionBounds(Rect)}.
*/
public void setSelectionBoundsHorizontalMargin(int left, int right) {
mSelectionBoundsMarginLeft = left;
mSelectionBoundsMarginRight = right;
}
/**
* Set drawable resources directly for both the background and the drawable resource
* of the photo view
*
* @param backgroundId Id of background resource
* @param drawableId Id of drawable resource
*/
public void setDrawableResource(int backgroundId, int drawableId) {
final ImageView photo = getPhotoView();
photo.setScaleType(ImageView.ScaleType.CENTER);
photo.setBackgroundResource(backgroundId);
photo.setImageResource(drawableId);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
if (mBoundsWithoutHeader.contains((int) x, (int) y)) {
return super.onTouchEvent(event);
} else {
return true;
}
}
}