blob: d20c6e8886f10c055e695a20e70f10803190eb61 [file] [log] [blame]
Chiao Cheng89437e82012-11-01 13:41:51 -07001/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.common.list;
18
19import android.content.Context;
20import android.content.res.ColorStateList;
21import android.content.res.TypedArray;
22import android.database.CharArrayBuffer;
23import android.database.Cursor;
24import android.graphics.Canvas;
Andrew Lee7b0cbeb2014-05-27 17:54:36 -070025import android.graphics.Color;
Chiao Cheng89437e82012-11-01 13:41:51 -070026import android.graphics.Rect;
27import android.graphics.Typeface;
28import android.graphics.drawable.Drawable;
29import android.os.Bundle;
30import android.provider.ContactsContract;
31import android.provider.ContactsContract.Contacts;
Walter Jang54564402016-01-18 11:56:19 -080032import android.provider.ContactsContract.SearchSnippets;
Wenyi Wang34d81ed2015-11-19 15:06:56 -080033import android.support.v4.content.ContextCompat;
Marcus Hagerott2aa31982016-10-25 14:36:25 -070034import android.support.v4.content.res.ResourcesCompat;
Wenyi Wang54ea4b12015-12-16 14:18:59 -080035import android.support.v4.graphics.drawable.DrawableCompat;
Wenyi Wange4e359f2016-04-02 14:30:02 -070036import android.support.v7.widget.AppCompatCheckBox;
Walter Jang379ff852016-05-22 12:17:39 -070037import android.support.v7.widget.AppCompatImageButton;
Chiao Cheng89437e82012-11-01 13:41:51 -070038import android.text.Spannable;
39import android.text.SpannableString;
40import android.text.TextUtils;
41import android.text.TextUtils.TruncateAt;
42import android.util.AttributeSet;
Sai Cheemalapati7c776a52014-05-27 16:37:15 -070043import android.util.TypedValue;
Chiao Cheng89437e82012-11-01 13:41:51 -070044import android.view.Gravity;
Yorke Lee1c171fa2013-09-06 13:53:49 -070045import android.view.MotionEvent;
Chiao Cheng89437e82012-11-01 13:41:51 -070046import android.view.View;
47import android.view.ViewGroup;
48import android.widget.AbsListView.SelectionBoundsAdjuster;
49import android.widget.ImageView;
50import android.widget.ImageView.ScaleType;
51import android.widget.QuickContactBadge;
52import android.widget.TextView;
53
54import com.android.contacts.common.ContactPresenceIconUtil;
55import com.android.contacts.common.ContactStatusUtil;
56import com.android.contacts.common.R;
Wenyi Wang54ea4b12015-12-16 14:18:59 -080057import com.android.contacts.common.compat.CompatUtils;
Wenyi Wang40346982015-11-18 14:58:42 -080058import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
Christine Chen3efbe592013-07-08 18:05:03 -070059import com.android.contacts.common.format.TextHighlighter;
Walter Jang1a21fe52014-10-15 18:32:44 -070060import com.android.contacts.common.util.ContactDisplayUtils;
Chiao Chengecba27e2012-12-27 17:14:53 -080061import com.android.contacts.common.util.SearchUtil;
Yorke Lee50a89a52013-11-04 14:44:30 -080062import com.android.contacts.common.util.ViewUtil;
63
Chiao Cheng901c5e52012-12-19 16:06:37 -080064import com.google.common.collect.Lists;
65
66import java.util.ArrayList;
67import java.util.List;
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -070068import java.util.Locale;
Chiao Cheng901c5e52012-12-19 16:06:37 -080069import java.util.regex.Matcher;
70import java.util.regex.Pattern;
Chiao Cheng89437e82012-11-01 13:41:51 -070071
72/**
73 * A custom view for an item in the contact list.
74 * The view contains the contact's photo, a set of text views (for name, status, etc...) and
75 * icons for presence and call.
76 * The view uses no XML file for layout and all the measurements and layouts are done
77 * in the onMeasure and onLayout methods.
78 *
79 * The layout puts the contact's photo on the right side of the view, the call icon (if present)
80 * to the left of the photo, the text lines are aligned to the left and the presence icon (if
81 * present) is set to the left of the status line.
82 *
83 * The layout also supports a header (used as a header of a group of contacts) that is above the
84 * contact's data and a divider between contact view.
85 */
86
87public class ContactListItemView extends ViewGroup
88 implements SelectionBoundsAdjuster {
89
Tyler Gunn001d9742015-12-18 13:57:02 -080090 private static final String TAG = "ContactListItemView";
91
Chiao Cheng89437e82012-11-01 13:41:51 -070092 // Style values for layout and appearance
93 // The initialized values are defaults if none is provided through xml.
94 private int mPreferredHeight = 0;
95 private int mGapBetweenImageAndText = 0;
Wenyi Wangb8b2fa32016-05-23 15:52:23 -070096 private int mGapBetweenIndexerAndImage = 0;
Chiao Cheng89437e82012-11-01 13:41:51 -070097 private int mGapBetweenLabelAndData = 0;
98 private int mPresenceIconMargin = 4;
99 private int mPresenceIconSize = 16;
Chiao Cheng89437e82012-11-01 13:41:51 -0700100 private int mTextIndent = 0;
Andrew Leeba448962014-05-29 16:20:19 -0700101 private int mTextOffsetTop;
guanxiongliu4d7715b2016-07-27 18:47:30 -0700102 private int mAvatarOffsetTop;
Sai Cheemalapati7c776a52014-05-27 16:37:15 -0700103 private int mNameTextViewTextSize;
Andrew Lee020ba622014-04-25 15:26:35 -0700104 private int mHeaderWidth;
Chiao Cheng89437e82012-11-01 13:41:51 -0700105 private Drawable mActivatedBackgroundDrawable;
Tyler Gunn001d9742015-12-18 13:57:02 -0800106 private int mVideoCallIconSize = 32;
107 private int mVideoCallIconMargin = 16;
Tingting Wange8086212016-07-20 22:17:17 -0700108 private int mGapFromScrollBar = 20;
Chiao Cheng89437e82012-11-01 13:41:51 -0700109
Sai Cheemalapatid814b032014-06-30 14:46:25 -0700110 // Set in onLayout. Represent left and right position of the View on the screen.
111 private int mLeftOffset;
112 private int mRightOffset;
113
Chiao Cheng89437e82012-11-01 13:41:51 -0700114 /**
115 * Used with {@link #mLabelView}, specifying the width ratio between label and data.
116 */
117 private int mLabelViewWidthWeight = 3;
118 /**
119 * Used with {@link #mDataView}, specifying the width ratio between label and data.
120 */
121 private int mDataViewWidthWeight = 5;
122
Christine Chenccba9502013-07-12 12:04:54 -0700123 protected static class HighlightSequence {
124 private final int start;
125 private final int end;
126
127 HighlightSequence(int start, int end) {
128 this.start = start;
129 this.end = end;
130 }
131 }
132
133 private ArrayList<HighlightSequence> mNameHighlightSequence;
134 private ArrayList<HighlightSequence> mNumberHighlightSequence;
Christine Chen3efbe592013-07-08 18:05:03 -0700135
136 // Highlighting prefix for names.
137 private String mHighlightedPrefix;
138
Chiao Cheng89437e82012-11-01 13:41:51 -0700139 /**
Tyler Gunn001d9742015-12-18 13:57:02 -0800140 * Used to notify listeners when a video call icon is clicked.
141 */
142 private PhoneNumberListAdapter.Listener mPhoneNumberListAdapterListener;
143
144 /**
145 * Indicates whether to show the "video call" icon, used to initiate a video call.
146 */
147 private boolean mShowVideoCallIcon = false;
148
149 /**
150 * Indicates whether the view should leave room for the "video call" icon.
151 */
152 private boolean mSupportVideoCallIcon = false;
153
154 /**
Chiao Cheng89437e82012-11-01 13:41:51 -0700155 * Where to put contact photo. This affects the other Views' layout or look-and-feel.
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700156 *
157 * TODO: replace enum with int constants
Chiao Cheng89437e82012-11-01 13:41:51 -0700158 */
159 public enum PhotoPosition {
160 LEFT,
161 RIGHT
162 }
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700163
164 static public final PhotoPosition getDefaultPhotoPosition(boolean opposite) {
165 final Locale locale = Locale.getDefault();
166 final int layoutDirection = TextUtils.getLayoutDirectionFromLocale(locale);
167 switch (layoutDirection) {
168 case View.LAYOUT_DIRECTION_RTL:
Andrew Leea88f4b22014-04-25 13:24:03 -0700169 return (opposite ? PhotoPosition.LEFT : PhotoPosition.RIGHT);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700170 case View.LAYOUT_DIRECTION_LTR:
171 default:
Andrew Leea88f4b22014-04-25 13:24:03 -0700172 return (opposite ? PhotoPosition.RIGHT : PhotoPosition.LEFT);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700173 }
174 }
175
176 private PhotoPosition mPhotoPosition = getDefaultPhotoPosition(false /* normal/non opposite */);
Chiao Cheng89437e82012-11-01 13:41:51 -0700177
178 // Header layout data
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700179 private View mHeaderView;
Andrew Lee020ba622014-04-25 15:26:35 -0700180 private boolean mIsSectionHeaderEnabled;
Chiao Cheng89437e82012-11-01 13:41:51 -0700181
182 // The views inside the contact view
183 private boolean mQuickContactEnabled = true;
184 private QuickContactBadge mQuickContact;
185 private ImageView mPhotoView;
186 private TextView mNameTextView;
187 private TextView mPhoneticNameTextView;
188 private TextView mLabelView;
189 private TextView mDataView;
190 private TextView mSnippetView;
191 private TextView mStatusView;
Chiao Cheng89437e82012-11-01 13:41:51 -0700192 private ImageView mPresenceIcon;
Wenyi Wange4e359f2016-04-02 14:30:02 -0700193 private AppCompatCheckBox mCheckBox;
Walter Jang379ff852016-05-22 12:17:39 -0700194 private AppCompatImageButton mDeleteImageButton;
Tyler Gunn001d9742015-12-18 13:57:02 -0800195 private ImageView mVideoCallIcon;
Victor Chang3e21eef2015-12-24 13:17:42 +0800196 private ImageView mWorkProfileIcon;
Chiao Cheng89437e82012-11-01 13:41:51 -0700197
198 private ColorStateList mSecondaryTextColor;
199
Chiao Cheng89437e82012-11-01 13:41:51 -0700200 private int mDefaultPhotoViewSize = 0;
201 /**
202 * Can be effective even when {@link #mPhotoView} is null, as we want to have horizontal padding
203 * to align other data in this View.
204 */
205 private int mPhotoViewWidth;
206 /**
207 * Can be effective even when {@link #mPhotoView} is null, as we want to have vertical padding.
208 */
209 private int mPhotoViewHeight;
210
211 /**
212 * Only effective when {@link #mPhotoView} is null.
213 * When true all the Views on the right side of the photo should have horizontal padding on
214 * those left assuming there is a photo.
215 */
216 private boolean mKeepHorizontalPaddingForPhotoView;
217 /**
218 * Only effective when {@link #mPhotoView} is null.
219 */
220 private boolean mKeepVerticalPaddingForPhotoView;
221
222 /**
223 * True when {@link #mPhotoViewWidth} and {@link #mPhotoViewHeight} are ready for being used.
224 * False indicates those values should be updated before being used in position calculation.
225 */
226 private boolean mPhotoViewWidthAndHeightAreReady = false;
227
228 private int mNameTextViewHeight;
Andrew Lee7b0cbeb2014-05-27 17:54:36 -0700229 private int mNameTextViewTextColor = Color.BLACK;
Chiao Cheng89437e82012-11-01 13:41:51 -0700230 private int mPhoneticNameTextViewHeight;
231 private int mLabelViewHeight;
232 private int mDataViewHeight;
233 private int mSnippetTextViewHeight;
234 private int mStatusTextViewHeight;
Brian Attwell207a8772015-02-27 16:35:00 -0800235 private int mCheckBoxHeight;
236 private int mCheckBoxWidth;
Walter Jang379ff852016-05-22 12:17:39 -0700237 private int mDeleteImageButtonHeight;
238 private int mDeleteImageButtonWidth;
Chiao Cheng89437e82012-11-01 13:41:51 -0700239
240 // Holds Math.max(mLabelTextViewHeight, mDataViewHeight), assuming Label and Data share the
241 // same row.
242 private int mLabelAndDataViewMaxHeight;
243
244 // TODO: some TextView fields are using CharArrayBuffer while some are not. Determine which is
245 // more efficient for each case or in general, and simplify the whole implementation.
246 // Note: if we're sure MARQUEE will be used every time, there's no reason to use
247 // CharArrayBuffer, since MARQUEE requires Span and thus we need to copy characters inside the
248 // buffer to Spannable once, while CharArrayBuffer is for directly applying char array to
249 // TextView without any modification.
250 private final CharArrayBuffer mDataBuffer = new CharArrayBuffer(128);
251 private final CharArrayBuffer mPhoneticNameBuffer = new CharArrayBuffer(128);
252
253 private boolean mActivatedStateSupported;
Andrew Lee4683e542014-06-09 16:24:10 -0700254 private boolean mAdjustSelectionBoundsEnabled = true;
Chiao Cheng89437e82012-11-01 13:41:51 -0700255
256 private Rect mBoundsWithoutHeader = new Rect();
257
258 /** A helper used to highlight a prefix in a text field. */
Christine Chen3efbe592013-07-08 18:05:03 -0700259 private final TextHighlighter mTextHighlighter;
Chiao Cheng89437e82012-11-01 13:41:51 -0700260 private CharSequence mUnknownNameText;
Tyler Gunn001d9742015-12-18 13:57:02 -0800261 private int mPosition;
Chiao Cheng89437e82012-11-01 13:41:51 -0700262
263 public ContactListItemView(Context context) {
264 super(context);
Chiao Cheng89437e82012-11-01 13:41:51 -0700265
Yorke Lee8b7f84a2013-09-12 12:38:41 -0700266 mTextHighlighter = new TextHighlighter(Typeface.BOLD);
Yorke Lee426e5d42014-03-18 13:07:00 -0700267 mNameHighlightSequence = new ArrayList<HighlightSequence>();
268 mNumberHighlightSequence = new ArrayList<HighlightSequence>();
Chiao Cheng89437e82012-11-01 13:41:51 -0700269 }
270
Tyler Gunn001d9742015-12-18 13:57:02 -0800271 public ContactListItemView(Context context, AttributeSet attrs, boolean supportVideoCallIcon) {
272 this(context, attrs);
273
274 mSupportVideoCallIcon = supportVideoCallIcon;
275 }
276
Chiao Cheng89437e82012-11-01 13:41:51 -0700277 public ContactListItemView(Context context, AttributeSet attrs) {
278 super(context, attrs);
Chiao Cheng89437e82012-11-01 13:41:51 -0700279
Yorke Leea675e3a2015-10-26 19:43:22 -0700280 TypedArray a;
Chiao Cheng89437e82012-11-01 13:41:51 -0700281
Yorke Leea675e3a2015-10-26 19:43:22 -0700282 if (R.styleable.ContactListItemView != null) {
283 // Read all style values
284 a = getContext().obtainStyledAttributes(attrs, R.styleable.ContactListItemView);
285 mPreferredHeight = a.getDimensionPixelSize(
286 R.styleable.ContactListItemView_list_item_height, mPreferredHeight);
287 mActivatedBackgroundDrawable = a.getDrawable(
288 R.styleable.ContactListItemView_activated_background);
Chiao Cheng89437e82012-11-01 13:41:51 -0700289
Yorke Leea675e3a2015-10-26 19:43:22 -0700290 mGapBetweenImageAndText = a.getDimensionPixelOffset(
291 R.styleable.ContactListItemView_list_item_gap_between_image_and_text,
292 mGapBetweenImageAndText);
Wenyi Wangb8b2fa32016-05-23 15:52:23 -0700293 mGapBetweenIndexerAndImage = a.getDimensionPixelOffset(
294 R.styleable.ContactListItemView_list_item_gap_between_indexer_and_image,
295 mGapBetweenIndexerAndImage);
Yorke Leea675e3a2015-10-26 19:43:22 -0700296 mGapBetweenLabelAndData = a.getDimensionPixelOffset(
297 R.styleable.ContactListItemView_list_item_gap_between_label_and_data,
298 mGapBetweenLabelAndData);
299 mPresenceIconMargin = a.getDimensionPixelOffset(
300 R.styleable.ContactListItemView_list_item_presence_icon_margin,
301 mPresenceIconMargin);
302 mPresenceIconSize = a.getDimensionPixelOffset(
303 R.styleable.ContactListItemView_list_item_presence_icon_size,
304 mPresenceIconSize);
305 mDefaultPhotoViewSize = a.getDimensionPixelOffset(
306 R.styleable.ContactListItemView_list_item_photo_size, mDefaultPhotoViewSize);
307 mTextIndent = a.getDimensionPixelOffset(
308 R.styleable.ContactListItemView_list_item_text_indent, mTextIndent);
309 mTextOffsetTop = a.getDimensionPixelOffset(
310 R.styleable.ContactListItemView_list_item_text_offset_top, mTextOffsetTop);
guanxiongliu4d7715b2016-07-27 18:47:30 -0700311 mAvatarOffsetTop = a.getDimensionPixelOffset(
312 R.styleable.ContactListItemView_list_item_avatar_offset_top, mAvatarOffsetTop);
Yorke Leea675e3a2015-10-26 19:43:22 -0700313 mDataViewWidthWeight = a.getInteger(
314 R.styleable.ContactListItemView_list_item_data_width_weight,
315 mDataViewWidthWeight);
316 mLabelViewWidthWeight = a.getInteger(
317 R.styleable.ContactListItemView_list_item_label_width_weight,
318 mLabelViewWidthWeight);
319 mNameTextViewTextColor = a.getColor(
320 R.styleable.ContactListItemView_list_item_name_text_color,
321 mNameTextViewTextColor);
322 mNameTextViewTextSize = (int) a.getDimension(
323 R.styleable.ContactListItemView_list_item_name_text_size,
324 (int) getResources().getDimension(R.dimen.contact_browser_list_item_text_size));
Tyler Gunn001d9742015-12-18 13:57:02 -0800325 mVideoCallIconSize = a.getDimensionPixelOffset(
326 R.styleable.ContactListItemView_list_item_video_call_icon_size,
327 mVideoCallIconSize);
328 mVideoCallIconMargin = a.getDimensionPixelOffset(
329 R.styleable.ContactListItemView_list_item_video_call_icon_margin,
330 mVideoCallIconMargin);
331
Yorke Leea675e3a2015-10-26 19:43:22 -0700332
333 setPaddingRelative(
334 a.getDimensionPixelOffset(
335 R.styleable.ContactListItemView_list_item_padding_left, 0),
336 a.getDimensionPixelOffset(
337 R.styleable.ContactListItemView_list_item_padding_top, 0),
338 a.getDimensionPixelOffset(
339 R.styleable.ContactListItemView_list_item_padding_right, 0),
340 a.getDimensionPixelOffset(
341 R.styleable.ContactListItemView_list_item_padding_bottom, 0));
342
343 a.recycle();
344 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700345
Yorke Lee8b7f84a2013-09-12 12:38:41 -0700346 mTextHighlighter = new TextHighlighter(Typeface.BOLD);
347
Yorke Leea675e3a2015-10-26 19:43:22 -0700348 if (R.styleable.Theme != null) {
349 a = getContext().obtainStyledAttributes(R.styleable.Theme);
350 mSecondaryTextColor = a.getColorStateList(R.styleable.Theme_android_textColorSecondary);
351 a.recycle();
352 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700353
Andrew Lee020ba622014-04-25 15:26:35 -0700354 mHeaderWidth =
355 getResources().getDimensionPixelSize(R.dimen.contact_list_section_header_width);
356
Chiao Cheng89437e82012-11-01 13:41:51 -0700357 if (mActivatedBackgroundDrawable != null) {
358 mActivatedBackgroundDrawable.setCallback(this);
359 }
Christine Chenccba9502013-07-12 12:04:54 -0700360
361 mNameHighlightSequence = new ArrayList<HighlightSequence>();
362 mNumberHighlightSequence = new ArrayList<HighlightSequence>();
Yorke Lee525f5132014-07-30 18:59:28 -0700363
364 setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE);
Chiao Cheng89437e82012-11-01 13:41:51 -0700365 }
366
367 public void setUnknownNameText(CharSequence unknownNameText) {
368 mUnknownNameText = unknownNameText;
369 }
370
371 public void setQuickContactEnabled(boolean flag) {
372 mQuickContactEnabled = flag;
373 }
374
Tyler Gunn001d9742015-12-18 13:57:02 -0800375 /**
376 * Sets whether the video calling icon is shown. For the video calling icon to be shown,
377 * {@link #mSupportVideoCallIcon} must be {@code true}.
378 *
379 * @param showVideoCallIcon {@code true} if the video calling icon is shown, {@code false}
380 * otherwise.
381 * @param listener Listener to notify when the video calling icon is clicked.
382 * @param position The position in the adapater of the video calling icon.
383 */
384 public void setShowVideoCallIcon(boolean showVideoCallIcon,
385 PhoneNumberListAdapter.Listener listener, int position) {
386 mShowVideoCallIcon = showVideoCallIcon;
387 mPhoneNumberListAdapterListener = listener;
388 mPosition = position;
389
390 if (mShowVideoCallIcon) {
391 if (mVideoCallIcon == null) {
392 mVideoCallIcon = new ImageView(getContext());
393 addView(mVideoCallIcon);
394 }
395 mVideoCallIcon.setContentDescription(getContext().getString(
396 R.string.description_search_video_call));
397 mVideoCallIcon.setImageResource(R.drawable.ic_search_video_call);
398 mVideoCallIcon.setScaleType(ScaleType.CENTER);
399 mVideoCallIcon.setVisibility(View.VISIBLE);
400 mVideoCallIcon.setOnClickListener(new OnClickListener() {
401 @Override
402 public void onClick(View v) {
403 // Inform the adapter that the video calling icon was clicked.
404 if (mPhoneNumberListAdapterListener != null) {
405 mPhoneNumberListAdapterListener.onVideoCallIconClicked(mPosition);
406 }
407 }
408 });
409 } else {
410 if (mVideoCallIcon != null) {
411 mVideoCallIcon.setVisibility(View.GONE);
412 }
413 }
414 }
415
416 /**
417 * Sets whether the view supports a video calling icon. This is independent of whether the view
418 * is actually showing an icon. Support for the video calling icon ensures that the layout
419 * leaves space for the video icon, should it be shown.
420 *
421 * @param supportVideoCallIcon {@code true} if the video call icon is supported, {@code false}
422 * otherwise.
423 */
424 public void setSupportVideoCallIcon(boolean supportVideoCallIcon) {
425 mSupportVideoCallIcon = supportVideoCallIcon;
426 }
427
Chiao Cheng89437e82012-11-01 13:41:51 -0700428 @Override
429 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
430 // We will match parent's width and wrap content vertically, but make sure
431 // height is no less than listPreferredItemHeight.
432 final int specWidth = resolveSize(0, widthMeasureSpec);
Andrew Leecd366f32014-04-22 15:44:37 -0700433 final int preferredHeight = mPreferredHeight;
Chiao Cheng89437e82012-11-01 13:41:51 -0700434
435 mNameTextViewHeight = 0;
436 mPhoneticNameTextViewHeight = 0;
437 mLabelViewHeight = 0;
438 mDataViewHeight = 0;
439 mLabelAndDataViewMaxHeight = 0;
440 mSnippetTextViewHeight = 0;
441 mStatusTextViewHeight = 0;
Brian Attwell207a8772015-02-27 16:35:00 -0800442 mCheckBoxWidth = 0;
443 mCheckBoxHeight = 0;
Walter Jang379ff852016-05-22 12:17:39 -0700444 mDeleteImageButtonWidth = 0;
445 mDeleteImageButtonHeight = 0;
Chiao Cheng89437e82012-11-01 13:41:51 -0700446
447 ensurePhotoViewSize();
448
449 // Width each TextView is able to use.
Andrew Leedd28d832014-04-28 18:38:31 -0700450 int effectiveWidth;
Chiao Cheng89437e82012-11-01 13:41:51 -0700451 // All the other Views will honor the photo, so available width for them may be shrunk.
452 if (mPhotoViewWidth > 0 || mKeepHorizontalPaddingForPhotoView) {
453 effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight()
Sean Midfordae1b8d32016-10-25 19:25:47 -0700454 - (mPhotoViewWidth + mGapBetweenImageAndText + mGapBetweenIndexerAndImage);
Chiao Cheng89437e82012-11-01 13:41:51 -0700455 } else {
456 effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight();
457 }
458
Andrew Leedd28d832014-04-28 18:38:31 -0700459 if (mIsSectionHeaderEnabled) {
Sean Midfordae1b8d32016-10-25 19:25:47 -0700460 effectiveWidth -= mHeaderWidth;
Andrew Leedd28d832014-04-28 18:38:31 -0700461 }
462
Tyler Gunn001d9742015-12-18 13:57:02 -0800463 if (mSupportVideoCallIcon) {
464 effectiveWidth -= (mVideoCallIconSize + mVideoCallIconMargin);
465 }
466
Chiao Cheng89437e82012-11-01 13:41:51 -0700467 // Go over all visible text views and measure actual width of each of them.
468 // Also calculate their heights to get the total height for this entire view.
469
Brian Attwell207a8772015-02-27 16:35:00 -0800470 if (isVisible(mCheckBox)) {
471 mCheckBox.measure(
472 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
473 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
474 mCheckBoxWidth = mCheckBox.getMeasuredWidth();
475 mCheckBoxHeight = mCheckBox.getMeasuredHeight();
476 effectiveWidth -= mCheckBoxWidth + mGapBetweenImageAndText;
477 }
478
Walter Jang379ff852016-05-22 12:17:39 -0700479 if (isVisible(mDeleteImageButton)) {
480 mDeleteImageButton.measure(
481 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
482 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
483 mDeleteImageButtonWidth = mDeleteImageButton.getMeasuredWidth();
484 mDeleteImageButtonHeight = mDeleteImageButton.getMeasuredHeight();
485 effectiveWidth -= mDeleteImageButtonWidth + mGapBetweenImageAndText;
486 }
487
Chiao Cheng89437e82012-11-01 13:41:51 -0700488 if (isVisible(mNameTextView)) {
Paul Soulos2b1334422014-05-27 11:28:00 -0700489 // Calculate width for name text - this parallels similar measurement in onLayout.
Chiao Cheng89437e82012-11-01 13:41:51 -0700490 int nameTextWidth = effectiveWidth;
491 if (mPhotoPosition != PhotoPosition.LEFT) {
492 nameTextWidth -= mTextIndent;
493 }
494 mNameTextView.measure(
495 MeasureSpec.makeMeasureSpec(nameTextWidth, MeasureSpec.EXACTLY),
496 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
497 mNameTextViewHeight = mNameTextView.getMeasuredHeight();
498 }
499
500 if (isVisible(mPhoneticNameTextView)) {
501 mPhoneticNameTextView.measure(
502 MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
503 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
504 mPhoneticNameTextViewHeight = mPhoneticNameTextView.getMeasuredHeight();
505 }
506
507 // If both data (phone number/email address) and label (type like "MOBILE") are quite long,
508 // we should ellipsize both using appropriate ratio.
509 final int dataWidth;
510 final int labelWidth;
511 if (isVisible(mDataView)) {
512 if (isVisible(mLabelView)) {
513 final int totalWidth = effectiveWidth - mGapBetweenLabelAndData;
514 dataWidth = ((totalWidth * mDataViewWidthWeight)
515 / (mDataViewWidthWeight + mLabelViewWidthWeight));
516 labelWidth = ((totalWidth * mLabelViewWidthWeight) /
517 (mDataViewWidthWeight + mLabelViewWidthWeight));
518 } else {
519 dataWidth = effectiveWidth;
520 labelWidth = 0;
521 }
522 } else {
523 dataWidth = 0;
524 if (isVisible(mLabelView)) {
525 labelWidth = effectiveWidth;
526 } else {
527 labelWidth = 0;
528 }
529 }
530
531 if (isVisible(mDataView)) {
532 mDataView.measure(MeasureSpec.makeMeasureSpec(dataWidth, MeasureSpec.EXACTLY),
533 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
534 mDataViewHeight = mDataView.getMeasuredHeight();
535 }
536
537 if (isVisible(mLabelView)) {
Tyler Gunn001d9742015-12-18 13:57:02 -0800538 mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, MeasureSpec.AT_MOST),
Chiao Cheng89437e82012-11-01 13:41:51 -0700539 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
540 mLabelViewHeight = mLabelView.getMeasuredHeight();
541 }
542 mLabelAndDataViewMaxHeight = Math.max(mLabelViewHeight, mDataViewHeight);
543
544 if (isVisible(mSnippetView)) {
545 mSnippetView.measure(
546 MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.EXACTLY),
547 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
548 mSnippetTextViewHeight = mSnippetView.getMeasuredHeight();
549 }
550
551 // Status view height is the biggest of the text view and the presence icon
552 if (isVisible(mPresenceIcon)) {
553 mPresenceIcon.measure(
554 MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY),
555 MeasureSpec.makeMeasureSpec(mPresenceIconSize, MeasureSpec.EXACTLY));
556 mStatusTextViewHeight = mPresenceIcon.getMeasuredHeight();
557 }
558
Tyler Gunn001d9742015-12-18 13:57:02 -0800559 if (mSupportVideoCallIcon && isVisible(mVideoCallIcon)) {
560 mVideoCallIcon.measure(
561 MeasureSpec.makeMeasureSpec(mVideoCallIconSize, MeasureSpec.EXACTLY),
562 MeasureSpec.makeMeasureSpec(mVideoCallIconSize, MeasureSpec.EXACTLY));
563 }
564
Victor Chang3e21eef2015-12-24 13:17:42 +0800565 if (isVisible(mWorkProfileIcon)) {
566 mWorkProfileIcon.measure(
567 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
568 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
569 mNameTextViewHeight =
570 Math.max(mNameTextViewHeight, mWorkProfileIcon.getMeasuredHeight());
571 }
572
Chiao Cheng89437e82012-11-01 13:41:51 -0700573 if (isVisible(mStatusView)) {
574 // Presence and status are in a same row, so status will be affected by icon size.
575 final int statusWidth;
576 if (isVisible(mPresenceIcon)) {
577 statusWidth = (effectiveWidth - mPresenceIcon.getMeasuredWidth()
578 - mPresenceIconMargin);
579 } else {
580 statusWidth = effectiveWidth;
581 }
582 mStatusView.measure(MeasureSpec.makeMeasureSpec(statusWidth, MeasureSpec.EXACTLY),
583 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
584 mStatusTextViewHeight =
585 Math.max(mStatusTextViewHeight, mStatusView.getMeasuredHeight());
586 }
587
588 // Calculate height including padding.
589 int height = (mNameTextViewHeight + mPhoneticNameTextViewHeight +
590 mLabelAndDataViewMaxHeight +
591 mSnippetTextViewHeight + mStatusTextViewHeight);
592
593 // Make sure the height is at least as high as the photo
594 height = Math.max(height, mPhotoViewHeight + getPaddingBottom() + getPaddingTop());
595
Chiao Cheng89437e82012-11-01 13:41:51 -0700596 // Make sure height is at least the preferred height
597 height = Math.max(height, preferredHeight);
598
Andrew Lee020ba622014-04-25 15:26:35 -0700599 // Measure the header if it is visible.
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700600 if (mHeaderView != null && mHeaderView.getVisibility() == VISIBLE) {
601 mHeaderView.measure(
Andrew Lee020ba622014-04-25 15:26:35 -0700602 MeasureSpec.makeMeasureSpec(mHeaderWidth, MeasureSpec.EXACTLY),
Andrew Lee815ce482014-05-30 12:28:17 -0700603 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
Chiao Cheng89437e82012-11-01 13:41:51 -0700604 }
605
606 setMeasuredDimension(specWidth, height);
607 }
608
609 @Override
610 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
611 final int height = bottom - top;
612 final int width = right - left;
613
614 // Determine the vertical bounds by laying out the header first.
615 int topBound = 0;
616 int bottomBound = height;
617 int leftBound = getPaddingLeft();
618 int rightBound = width - getPaddingRight();
619
Yorke Lee50a89a52013-11-04 14:44:30 -0800620 final boolean isLayoutRtl = ViewUtil.isViewLayoutRtl(this);
Yorke Lee72506b72013-02-05 16:25:54 -0800621
Andrew Lee020ba622014-04-25 15:26:35 -0700622 // Put the section header on the left side of the contact view.
623 if (mIsSectionHeaderEnabled) {
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700624 if (mHeaderView != null) {
625 int headerHeight = mHeaderView.getMeasuredHeight();
Andrew Lee815ce482014-05-30 12:28:17 -0700626 int headerTopBound = (bottomBound + topBound - headerHeight) / 2 + mTextOffsetTop;
627
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700628 mHeaderView.layout(
Andrew Lee020ba622014-04-25 15:26:35 -0700629 isLayoutRtl ? rightBound - mHeaderWidth : leftBound,
Andrew Lee815ce482014-05-30 12:28:17 -0700630 headerTopBound,
Andrew Lee020ba622014-04-25 15:26:35 -0700631 isLayoutRtl ? rightBound : leftBound + mHeaderWidth,
Andrew Lee815ce482014-05-30 12:28:17 -0700632 headerTopBound + headerHeight);
Andrew Lee020ba622014-04-25 15:26:35 -0700633 }
634 if (isLayoutRtl) {
635 rightBound -= mHeaderWidth;
636 } else {
637 leftBound += mHeaderWidth;
638 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700639 }
640
Sai Cheemalapatid814b032014-06-30 14:46:25 -0700641 mBoundsWithoutHeader.set(left + leftBound, topBound, left + rightBound, bottomBound);
642 mLeftOffset = left + leftBound;
643 mRightOffset = left + rightBound;
Wenyi Wangfd10e742016-07-25 18:02:50 -0700644 if (isLayoutRtl) {
645 rightBound -= mGapBetweenIndexerAndImage;
646 } else {
647 leftBound += mGapBetweenIndexerAndImage;
Andrew Leedd28d832014-04-28 18:38:31 -0700648 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700649
650 if (mActivatedStateSupported && isActivated()) {
651 mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
652 }
653
Brian Attwell207a8772015-02-27 16:35:00 -0800654 if (isVisible(mCheckBox)) {
655 final int photoTop = topBound + (bottomBound - topBound - mCheckBoxHeight) / 2;
656 if (mPhotoPosition == PhotoPosition.LEFT) {
Tingting Wange8086212016-07-20 22:17:17 -0700657 mCheckBox.layout(rightBound - mGapFromScrollBar - mCheckBoxWidth,
Brian Attwell207a8772015-02-27 16:35:00 -0800658 photoTop,
Tingting Wange8086212016-07-20 22:17:17 -0700659 rightBound - mGapFromScrollBar,
Brian Attwell207a8772015-02-27 16:35:00 -0800660 photoTop + mCheckBoxHeight);
661 } else {
Tingting Wange8086212016-07-20 22:17:17 -0700662 mCheckBox.layout(leftBound + mGapFromScrollBar,
Brian Attwell207a8772015-02-27 16:35:00 -0800663 photoTop,
Tingting Wange8086212016-07-20 22:17:17 -0700664 leftBound + mGapFromScrollBar + mCheckBoxWidth,
Brian Attwell207a8772015-02-27 16:35:00 -0800665 photoTop + mCheckBoxHeight);
666 }
667 }
668
Walter Jang379ff852016-05-22 12:17:39 -0700669 if (isVisible(mDeleteImageButton)) {
670 final int photoTop = topBound + (bottomBound - topBound - mDeleteImageButtonHeight) / 2;
Tingting Wang408b0232016-06-28 23:00:30 -0700671 final int mDeleteImageButtonSize = mDeleteImageButtonHeight > mDeleteImageButtonWidth
672 ? mDeleteImageButtonHeight : mDeleteImageButtonWidth;
Walter Jang379ff852016-05-22 12:17:39 -0700673 if (mPhotoPosition == PhotoPosition.LEFT) {
Tingting Wang408b0232016-06-28 23:00:30 -0700674 mDeleteImageButton.layout(rightBound - mDeleteImageButtonSize,
Walter Jang379ff852016-05-22 12:17:39 -0700675 photoTop,
676 rightBound,
Tingting Wang408b0232016-06-28 23:00:30 -0700677 photoTop + mDeleteImageButtonSize);
Wenyi Wangd348a902016-08-14 12:56:06 -0700678 rightBound -= mDeleteImageButtonSize;
Walter Jang379ff852016-05-22 12:17:39 -0700679 } else {
680 mDeleteImageButton.layout(leftBound,
681 photoTop,
Tingting Wang408b0232016-06-28 23:00:30 -0700682 leftBound + mDeleteImageButtonSize,
683 photoTop + mDeleteImageButtonSize);
Wenyi Wangd348a902016-08-14 12:56:06 -0700684 leftBound += mDeleteImageButtonSize;
Walter Jang379ff852016-05-22 12:17:39 -0700685 }
686 }
687
Chiao Cheng89437e82012-11-01 13:41:51 -0700688 final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
689 if (mPhotoPosition == PhotoPosition.LEFT) {
690 // Photo is the left most view. All the other Views should on the right of the photo.
691 if (photoView != null) {
692 // Center the photo vertically
guanxiongliu4d7715b2016-07-27 18:47:30 -0700693 final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2
694 + mAvatarOffsetTop;
Chiao Cheng89437e82012-11-01 13:41:51 -0700695 photoView.layout(
696 leftBound,
697 photoTop,
698 leftBound + mPhotoViewWidth,
699 photoTop + mPhotoViewHeight);
700 leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
701 } else if (mKeepHorizontalPaddingForPhotoView) {
702 // Draw nothing but keep the padding.
703 leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
704 }
705 } else {
706 // Photo is the right most view. Right bound should be adjusted that way.
707 if (photoView != null) {
708 // Center the photo vertically
guanxiongliu4d7715b2016-07-27 18:47:30 -0700709 final int photoTop = topBound + (bottomBound - topBound - mPhotoViewHeight) / 2
710 + mAvatarOffsetTop;
Chiao Cheng89437e82012-11-01 13:41:51 -0700711 photoView.layout(
712 rightBound - mPhotoViewWidth,
713 photoTop,
714 rightBound,
715 photoTop + mPhotoViewHeight);
716 rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700717 } else if (mKeepHorizontalPaddingForPhotoView) {
718 // Draw nothing but keep the padding.
719 rightBound -= (mPhotoViewWidth + mGapBetweenImageAndText);
Chiao Cheng89437e82012-11-01 13:41:51 -0700720 }
721
722 // Add indent between left-most padding and texts.
723 leftBound += mTextIndent;
724 }
725
Tyler Gunn001d9742015-12-18 13:57:02 -0800726 if (mSupportVideoCallIcon) {
727 // Place the video call button at the end of the list (e.g. take into account RTL mode).
728 if (isVisible(mVideoCallIcon)) {
729 // Center the video icon vertically
730 final int videoIconTop = topBound +
731 (bottomBound - topBound - mVideoCallIconSize) / 2;
732
733 if (!isLayoutRtl) {
734 // When photo is on left, video icon is placed on the right edge.
735 mVideoCallIcon.layout(rightBound - mVideoCallIconSize,
736 videoIconTop,
737 rightBound,
738 videoIconTop + mVideoCallIconSize);
739 } else {
740 // When photo is on right, video icon is placed on the left edge.
741 mVideoCallIcon.layout(leftBound,
742 videoIconTop,
743 leftBound + mVideoCallIconSize,
744 videoIconTop + mVideoCallIconSize);
745 }
746 }
747
748 if (mPhotoPosition == PhotoPosition.LEFT) {
749 rightBound -= (mVideoCallIconSize + mVideoCallIconMargin);
750 } else {
751 leftBound += mVideoCallIconSize + mVideoCallIconMargin;
752 }
753 }
754
755
Andrew Leeba448962014-05-29 16:20:19 -0700756 // Center text vertically, then apply the top offset.
Chiao Cheng89437e82012-11-01 13:41:51 -0700757 final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
758 mLabelAndDataViewMaxHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
Andrew Leeba448962014-05-29 16:20:19 -0700759 int textTopBound = (bottomBound + topBound - totalTextHeight) / 2 + mTextOffsetTop;
Chiao Cheng89437e82012-11-01 13:41:51 -0700760
Victor Chang3e21eef2015-12-24 13:17:42 +0800761 // Work Profile icon align top
762 int workProfileIconWidth = 0;
763 if (isVisible(mWorkProfileIcon)) {
764 workProfileIconWidth = mWorkProfileIcon.getMeasuredWidth();
765 final int distanceFromEnd = mCheckBoxWidth > 0
766 ? mCheckBoxWidth + mGapBetweenImageAndText : 0;
767 if (mPhotoPosition == PhotoPosition.LEFT) {
768 // When photo is on left, label is placed on the right edge of the list item.
769 mWorkProfileIcon.layout(rightBound - workProfileIconWidth - distanceFromEnd,
770 textTopBound,
771 rightBound - distanceFromEnd,
772 textTopBound + mNameTextViewHeight);
773 } else {
774 // When photo is on right, label is placed on the left of data view.
775 mWorkProfileIcon.layout(leftBound + distanceFromEnd,
776 textTopBound,
777 leftBound + workProfileIconWidth + distanceFromEnd,
778 textTopBound + mNameTextViewHeight);
779 }
780 }
781
Chiao Cheng89437e82012-11-01 13:41:51 -0700782 // Layout all text view and presence icon
783 // Put name TextView first
784 if (isVisible(mNameTextView)) {
Victor Chang3e21eef2015-12-24 13:17:42 +0800785 final int distanceFromEnd = workProfileIconWidth
786 + (mCheckBoxWidth > 0 ? mCheckBoxWidth + mGapBetweenImageAndText : 0);
Brian Attwell207a8772015-02-27 16:35:00 -0800787 if (mPhotoPosition == PhotoPosition.LEFT) {
788 mNameTextView.layout(leftBound,
789 textTopBound,
790 rightBound - distanceFromEnd,
791 textTopBound + mNameTextViewHeight);
792 } else {
793 mNameTextView.layout(leftBound + distanceFromEnd,
794 textTopBound,
795 rightBound,
796 textTopBound + mNameTextViewHeight);
797 }
Victor Chang3e21eef2015-12-24 13:17:42 +0800798 }
799
800 if (isVisible(mNameTextView) || isVisible(mWorkProfileIcon)) {
Chiao Cheng89437e82012-11-01 13:41:51 -0700801 textTopBound += mNameTextViewHeight;
802 }
803
804 // Presence and status
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700805 if (isLayoutRtl) {
806 int statusRightBound = rightBound;
807 if (isVisible(mPresenceIcon)) {
808 int iconWidth = mPresenceIcon.getMeasuredWidth();
809 mPresenceIcon.layout(
810 rightBound - iconWidth,
811 textTopBound,
812 rightBound,
813 textTopBound + mStatusTextViewHeight);
814 statusRightBound -= (iconWidth + mPresenceIconMargin);
815 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700816
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -0700817 if (isVisible(mStatusView)) {
818 mStatusView.layout(leftBound,
819 textTopBound,
820 statusRightBound,
821 textTopBound + mStatusTextViewHeight);
822 }
823 } else {
824 int statusLeftBound = leftBound;
825 if (isVisible(mPresenceIcon)) {
826 int iconWidth = mPresenceIcon.getMeasuredWidth();
827 mPresenceIcon.layout(
828 leftBound,
829 textTopBound,
830 leftBound + iconWidth,
831 textTopBound + mStatusTextViewHeight);
832 statusLeftBound += (iconWidth + mPresenceIconMargin);
833 }
834
835 if (isVisible(mStatusView)) {
836 mStatusView.layout(statusLeftBound,
837 textTopBound,
838 rightBound,
839 textTopBound + mStatusTextViewHeight);
840 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700841 }
842
843 if (isVisible(mStatusView) || isVisible(mPresenceIcon)) {
844 textTopBound += mStatusTextViewHeight;
845 }
846
847 // Rest of text views
848 int dataLeftBound = leftBound;
849 if (isVisible(mPhoneticNameTextView)) {
850 mPhoneticNameTextView.layout(leftBound,
851 textTopBound,
852 rightBound,
853 textTopBound + mPhoneticNameTextViewHeight);
854 textTopBound += mPhoneticNameTextViewHeight;
855 }
856
857 // Label and Data align bottom.
858 if (isVisible(mLabelView)) {
Tyler Gunn001d9742015-12-18 13:57:02 -0800859 if (!isLayoutRtl) {
860 mLabelView.layout(dataLeftBound,
861 textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
862 rightBound,
863 textTopBound + mLabelAndDataViewMaxHeight);
864 dataLeftBound += mLabelView.getMeasuredWidth() + mGapBetweenLabelAndData;
865 } else {
866 dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
Chiao Cheng89437e82012-11-01 13:41:51 -0700867 mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
868 textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
869 rightBound,
870 textTopBound + mLabelAndDataViewMaxHeight);
Tyler Gunn001d9742015-12-18 13:57:02 -0800871 rightBound -= (mLabelView.getMeasuredWidth() + mGapBetweenLabelAndData);
Chiao Cheng89437e82012-11-01 13:41:51 -0700872 }
873 }
874
875 if (isVisible(mDataView)) {
Tyler Gunn001d9742015-12-18 13:57:02 -0800876 if (!isLayoutRtl) {
877 mDataView.layout(dataLeftBound,
878 textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
879 rightBound,
880 textTopBound + mLabelAndDataViewMaxHeight);
881 } else {
882 mDataView.layout(rightBound - mDataView.getMeasuredWidth(),
883 textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
884 rightBound,
885 textTopBound + mLabelAndDataViewMaxHeight);
886 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700887 }
888 if (isVisible(mLabelView) || isVisible(mDataView)) {
889 textTopBound += mLabelAndDataViewMaxHeight;
890 }
891
892 if (isVisible(mSnippetView)) {
893 mSnippetView.layout(leftBound,
894 textTopBound,
895 rightBound,
896 textTopBound + mSnippetTextViewHeight);
897 }
898 }
899
900 @Override
901 public void adjustListItemSelectionBounds(Rect bounds) {
Andrew Lee4683e542014-06-09 16:24:10 -0700902 if (mAdjustSelectionBoundsEnabled) {
903 bounds.top += mBoundsWithoutHeader.top;
904 bounds.bottom = bounds.top + mBoundsWithoutHeader.height();
905 bounds.left = mBoundsWithoutHeader.left;
906 bounds.right = mBoundsWithoutHeader.right;
907 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700908 }
909
910 protected boolean isVisible(View view) {
911 return view != null && view.getVisibility() == View.VISIBLE;
912 }
913
914 /**
915 * Extracts width and height from the style
916 */
917 private void ensurePhotoViewSize() {
918 if (!mPhotoViewWidthAndHeightAreReady) {
919 mPhotoViewWidth = mPhotoViewHeight = getDefaultPhotoViewSize();
920 if (!mQuickContactEnabled && mPhotoView == null) {
921 if (!mKeepHorizontalPaddingForPhotoView) {
922 mPhotoViewWidth = 0;
923 }
924 if (!mKeepVerticalPaddingForPhotoView) {
925 mPhotoViewHeight = 0;
926 }
927 }
928
929 mPhotoViewWidthAndHeightAreReady = true;
930 }
931 }
932
Chiao Cheng89437e82012-11-01 13:41:51 -0700933 protected int getDefaultPhotoViewSize() {
934 return mDefaultPhotoViewSize;
935 }
936
937 /**
938 * Gets a LayoutParam that corresponds to the default photo size.
939 *
940 * @return A new LayoutParam.
941 */
942 private LayoutParams getDefaultPhotoLayoutParams() {
943 LayoutParams params = generateDefaultLayoutParams();
944 params.width = getDefaultPhotoViewSize();
945 params.height = params.width;
946 return params;
947 }
948
949 @Override
950 protected void drawableStateChanged() {
951 super.drawableStateChanged();
952 if (mActivatedStateSupported) {
953 mActivatedBackgroundDrawable.setState(getDrawableState());
954 }
955 }
956
957 @Override
958 protected boolean verifyDrawable(Drawable who) {
959 return who == mActivatedBackgroundDrawable || super.verifyDrawable(who);
960 }
961
962 @Override
963 public void jumpDrawablesToCurrentState() {
964 super.jumpDrawablesToCurrentState();
965 if (mActivatedStateSupported) {
966 mActivatedBackgroundDrawable.jumpToCurrentState();
967 }
968 }
969
970 @Override
971 public void dispatchDraw(Canvas canvas) {
972 if (mActivatedStateSupported && isActivated()) {
973 mActivatedBackgroundDrawable.draw(canvas);
974 }
Chiao Cheng89437e82012-11-01 13:41:51 -0700975
976 super.dispatchDraw(canvas);
977 }
978
979 /**
Chiao Cheng89437e82012-11-01 13:41:51 -0700980 * Sets section header or makes it invisible if the title is null.
981 */
982 public void setSectionHeader(String title) {
John Shaoda188712016-08-19 14:57:11 -0700983 if (title != null) {
984 // Empty section title is the favorites so show the star here.
985 if (title.isEmpty()) {
Wenyi Wang7d5be052016-07-25 14:39:52 -0700986 if (mHeaderView == null) {
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700987 addStarImageHeader();
Wenyi Wang7d5be052016-07-25 14:39:52 -0700988 } else if (mHeaderView instanceof TextView) {
989 removeView(mHeaderView);
990 addStarImageHeader();
991 } else {
992 mHeaderView.setVisibility(View.VISIBLE);
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700993 }
994 } else {
Wenyi Wang7d5be052016-07-25 14:39:52 -0700995 if (mHeaderView == null) {
996 addTextHeader(title);
997 } else if (mHeaderView instanceof ImageView) {
998 removeView(mHeaderView);
Wenyi Wangd5403fa2016-07-20 17:16:38 -0700999 addTextHeader(title);
1000 } else {
Wenyi Wang7d5be052016-07-25 14:39:52 -07001001 updateHeaderText((TextView) mHeaderView, title);
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001002 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001003 }
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001004 } else if (mHeaderView != null) {
1005 mHeaderView.setVisibility(View.GONE);
Chiao Cheng89437e82012-11-01 13:41:51 -07001006 }
1007 }
1008
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001009 private void addTextHeader(String title) {
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001010 mHeaderView = new TextView(getContext());
Wenyi Wang7d5be052016-07-25 14:39:52 -07001011 final TextView headerTextView = (TextView) mHeaderView;
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001012 headerTextView.setTextAppearance(getContext(), R.style.SectionHeaderStyle);
1013 headerTextView.setGravity(Gravity.CENTER_HORIZONTAL);
1014 updateHeaderText(headerTextView, title);
1015 addView(headerTextView);
1016 }
1017
1018 private void updateHeaderText(TextView headerTextView, String title) {
1019 setMarqueeText(headerTextView, title);
1020 headerTextView.setAllCaps(true);
1021 if (ContactsSectionIndexer.BLANK_HEADER_STRING.equals(title)) {
1022 headerTextView.setContentDescription(
1023 getContext().getString(R.string.description_no_name_header));
John Shao9c129d52016-07-21 19:43:08 -07001024 } else {
1025 headerTextView.setContentDescription(title);
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001026 }
1027 headerTextView.setVisibility(View.VISIBLE);
1028 }
1029
1030 private void addStarImageHeader() {
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001031 mHeaderView = new ImageView(getContext());
Wenyi Wang7d5be052016-07-25 14:39:52 -07001032 final ImageView headerImageView = (ImageView) mHeaderView;
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001033 headerImageView.setImageDrawable(
1034 getResources().getDrawable(R.drawable.ic_material_star, getContext().getTheme()));
1035 headerImageView.setImageTintList(ColorStateList.valueOf(getResources()
1036 .getColor(R.color.material_star_pink)));
1037 headerImageView.setContentDescription(
Wenyi Wang7438b132016-11-17 15:39:24 -08001038 getContext().getString(R.string.contactsFavoritesLabel));
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001039 headerImageView.setVisibility(View.VISIBLE);
1040 addView(headerImageView);
1041 }
1042
Andrew Lee020ba622014-04-25 15:26:35 -07001043 public void setIsSectionHeaderEnabled(boolean isSectionHeaderEnabled) {
1044 mIsSectionHeaderEnabled = isSectionHeaderEnabled;
1045 }
1046
Chiao Cheng89437e82012-11-01 13:41:51 -07001047 /**
1048 * Returns the quick contact badge, creating it if necessary.
1049 */
1050 public QuickContactBadge getQuickContact() {
1051 if (!mQuickContactEnabled) {
1052 throw new IllegalStateException("QuickContact is disabled for this view");
1053 }
1054 if (mQuickContact == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001055 mQuickContact = new QuickContactBadge(getContext());
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001056 if (CompatUtils.isLollipopCompatible()) {
1057 mQuickContact.setOverlay(null);
1058 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001059 mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams());
1060 if (mNameTextView != null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001061 mQuickContact.setContentDescription(getContext().getString(
Chiao Cheng89437e82012-11-01 13:41:51 -07001062 R.string.description_quick_contact_for, mNameTextView.getText()));
1063 }
1064
1065 addView(mQuickContact);
1066 mPhotoViewWidthAndHeightAreReady = false;
1067 }
1068 return mQuickContact;
1069 }
1070
1071 /**
1072 * Returns the photo view, creating it if necessary.
1073 */
1074 public ImageView getPhotoView() {
1075 if (mPhotoView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001076 mPhotoView = new ImageView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001077 mPhotoView.setLayoutParams(getDefaultPhotoLayoutParams());
1078 // Quick contact style used above will set a background - remove it
1079 mPhotoView.setBackground(null);
1080 addView(mPhotoView);
1081 mPhotoViewWidthAndHeightAreReady = false;
1082 }
1083 return mPhotoView;
1084 }
1085
1086 /**
1087 * Removes the photo view.
1088 */
1089 public void removePhotoView() {
1090 removePhotoView(false, true);
1091 }
1092
1093 /**
1094 * Removes the photo view.
1095 *
1096 * @param keepHorizontalPadding True means data on the right side will have
1097 * padding on left, pretending there is still a photo view.
1098 * @param keepVerticalPadding True means the View will have some height
1099 * enough for accommodating a photo view.
1100 */
1101 public void removePhotoView(boolean keepHorizontalPadding, boolean keepVerticalPadding) {
1102 mPhotoViewWidthAndHeightAreReady = false;
1103 mKeepHorizontalPaddingForPhotoView = keepHorizontalPadding;
1104 mKeepVerticalPaddingForPhotoView = keepVerticalPadding;
1105 if (mPhotoView != null) {
1106 removeView(mPhotoView);
1107 mPhotoView = null;
1108 }
1109 if (mQuickContact != null) {
1110 removeView(mQuickContact);
1111 mQuickContact = null;
1112 }
1113 }
1114
1115 /**
1116 * Sets a word prefix that will be highlighted if encountered in fields like
Christine Chen3efbe592013-07-08 18:05:03 -07001117 * name and search snippet. This will disable the mask highlighting for names.
Chiao Cheng89437e82012-11-01 13:41:51 -07001118 * <p>
1119 * NOTE: must be all upper-case
1120 */
Chiao Chenga1554ef2012-12-21 15:45:54 -08001121 public void setHighlightedPrefix(String upperCasePrefix) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001122 mHighlightedPrefix = upperCasePrefix;
Christine Chen3efbe592013-07-08 18:05:03 -07001123 }
1124
1125 /**
Christine Chenccba9502013-07-12 12:04:54 -07001126 * Clears previously set highlight sequences for the view.
Christine Chen3efbe592013-07-08 18:05:03 -07001127 */
Christine Chenccba9502013-07-12 12:04:54 -07001128 public void clearHighlightSequences() {
1129 mNameHighlightSequence.clear();
1130 mNumberHighlightSequence.clear();
Christine Chen3efbe592013-07-08 18:05:03 -07001131 mHighlightedPrefix = null;
1132 }
1133
1134 /**
Christine Chenccba9502013-07-12 12:04:54 -07001135 * Adds a highlight sequence to the name highlighter.
1136 * @param start The start position of the highlight sequence.
1137 * @param end The end position of the highlight sequence.
Christine Chen3efbe592013-07-08 18:05:03 -07001138 */
Christine Chenccba9502013-07-12 12:04:54 -07001139 public void addNameHighlightSequence(int start, int end) {
1140 mNameHighlightSequence.add(new HighlightSequence(start, end));
1141 }
1142
1143 /**
1144 * Adds a highlight sequence to the number highlighter.
1145 * @param start The start position of the highlight sequence.
1146 * @param end The end position of the highlight sequence.
1147 */
1148 public void addNumberHighlightSequence(int start, int end) {
1149 mNumberHighlightSequence.add(new HighlightSequence(start, end));
Chiao Cheng89437e82012-11-01 13:41:51 -07001150 }
1151
1152 /**
1153 * Returns the text view for the contact name, creating it if necessary.
1154 */
1155 public TextView getNameTextView() {
1156 if (mNameTextView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001157 mNameTextView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001158 mNameTextView.setSingleLine(true);
1159 mNameTextView.setEllipsize(getTextEllipsis());
Marcus Hagerott2aa31982016-10-25 14:36:25 -07001160 mNameTextView.setTextColor(ResourcesCompat.getColorStateList(getResources(),
1161 R.color.contact_list_name_text_color, getContext().getTheme()));
Wenyi Wangbaf26222016-07-11 22:24:41 -07001162 mNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mNameTextViewTextSize);
Chiao Cheng89437e82012-11-01 13:41:51 -07001163 // Manually call setActivated() since this view may be added after the first
1164 // setActivated() call toward this whole item view.
1165 mNameTextView.setActivated(isActivated());
1166 mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001167 mNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
1168 mNameTextView.setId(R.id.cliv_name_textview);
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001169 if (CompatUtils.isLollipopCompatible()) {
1170 mNameTextView.setElegantTextHeight(false);
1171 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001172 addView(mNameTextView);
1173 }
1174 return mNameTextView;
1175 }
1176
1177 /**
1178 * Adds or updates a text view for the phonetic name.
1179 */
1180 public void setPhoneticName(char[] text, int size) {
1181 if (text == null || size == 0) {
1182 if (mPhoneticNameTextView != null) {
1183 mPhoneticNameTextView.setVisibility(View.GONE);
1184 }
1185 } else {
1186 getPhoneticNameTextView();
1187 setMarqueeText(mPhoneticNameTextView, text, size);
1188 mPhoneticNameTextView.setVisibility(VISIBLE);
1189 }
1190 }
1191
1192 /**
1193 * Returns the text view for the phonetic name, creating it if necessary.
1194 */
1195 public TextView getPhoneticNameTextView() {
1196 if (mPhoneticNameTextView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001197 mPhoneticNameTextView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001198 mPhoneticNameTextView.setSingleLine(true);
1199 mPhoneticNameTextView.setEllipsize(getTextEllipsis());
Yorke Leef3f259c2013-10-21 15:06:18 -07001200 mPhoneticNameTextView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
Walter Jang0b2f8cf2014-11-12 13:45:19 -08001201 mPhoneticNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001202 mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
1203 mPhoneticNameTextView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001204 mPhoneticNameTextView.setId(R.id.cliv_phoneticname_textview);
Chiao Cheng89437e82012-11-01 13:41:51 -07001205 addView(mPhoneticNameTextView);
1206 }
1207 return mPhoneticNameTextView;
1208 }
1209
1210 /**
1211 * Adds or updates a text view for the data label.
1212 */
1213 public void setLabel(CharSequence text) {
1214 if (TextUtils.isEmpty(text)) {
1215 if (mLabelView != null) {
1216 mLabelView.setVisibility(View.GONE);
1217 }
1218 } else {
1219 getLabelView();
1220 setMarqueeText(mLabelView, text);
1221 mLabelView.setVisibility(VISIBLE);
1222 }
1223 }
1224
1225 /**
1226 * Returns the text view for the data label, creating it if necessary.
1227 */
1228 public TextView getLabelView() {
1229 if (mLabelView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001230 mLabelView = new TextView(getContext());
Tyler Gunn001d9742015-12-18 13:57:02 -08001231 mLabelView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
1232 LayoutParams.WRAP_CONTENT));
1233
Chiao Cheng89437e82012-11-01 13:41:51 -07001234 mLabelView.setSingleLine(true);
1235 mLabelView.setEllipsize(getTextEllipsis());
Yorke Lee796824d2013-12-02 15:41:42 -08001236 mLabelView.setTextAppearance(getContext(), R.style.TextAppearanceSmall);
Chiao Cheng89437e82012-11-01 13:41:51 -07001237 if (mPhotoPosition == PhotoPosition.LEFT) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001238 mLabelView.setAllCaps(true);
Chiao Cheng89437e82012-11-01 13:41:51 -07001239 } else {
1240 mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
1241 }
1242 mLabelView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001243 mLabelView.setId(R.id.cliv_label_textview);
Chiao Cheng89437e82012-11-01 13:41:51 -07001244 addView(mLabelView);
1245 }
1246 return mLabelView;
1247 }
1248
1249 /**
1250 * Adds or updates a text view for the data element.
1251 */
Christine Chen3efbe592013-07-08 18:05:03 -07001252 public void setData(char[] text, int size) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001253 if (text == null || size == 0) {
1254 if (mDataView != null) {
1255 mDataView.setVisibility(View.GONE);
1256 }
1257 } else {
1258 getDataView();
1259 setMarqueeText(mDataView, text, size);
1260 mDataView.setVisibility(VISIBLE);
Christine Chen3efbe592013-07-08 18:05:03 -07001261 }
1262 }
1263
1264 /**
1265 * Sets phone number for a list item. This takes care of number highlighting if the highlight
1266 * mask exists.
1267 */
Andrew Lee32e17da2014-03-31 12:44:34 -07001268 public void setPhoneNumber(String text, String countryIso) {
Christine Chen3efbe592013-07-08 18:05:03 -07001269 if (text == null) {
1270 if (mDataView != null) {
1271 mDataView.setVisibility(View.GONE);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001272 }
Christine Chen3efbe592013-07-08 18:05:03 -07001273 } else {
1274 getDataView();
Andrew Lee32e17da2014-03-31 12:44:34 -07001275
Yorke Lee69f471a2014-04-08 15:00:47 -07001276 // TODO: Format number using PhoneNumberUtils.formatNumber before assigning it to
1277 // mDataView. Make sure that determination of the highlight sequences are done only
1278 // after number formatting.
Andrew Lee32e17da2014-03-31 12:44:34 -07001279
Christine Chen3efbe592013-07-08 18:05:03 -07001280 // Sets phone number texts for display after highlighting it, if applicable.
Andrew Lee32e17da2014-03-31 12:44:34 -07001281 // CharSequence textToSet = text;
Christine Chenccba9502013-07-12 12:04:54 -07001282 final SpannableString textToSet = new SpannableString(text);
1283
1284 if (mNumberHighlightSequence.size() != 0) {
1285 final HighlightSequence highlightSequence = mNumberHighlightSequence.get(0);
1286 mTextHighlighter.applyMaskingHighlight(textToSet, highlightSequence.start,
1287 highlightSequence.end);
Christine Chen3efbe592013-07-08 18:05:03 -07001288 }
Christine Chenccba9502013-07-12 12:04:54 -07001289
Christine Chen3efbe592013-07-08 18:05:03 -07001290 setMarqueeText(mDataView, textToSet);
1291 mDataView.setVisibility(VISIBLE);
1292
1293 // We have a phone number as "mDataView" so make it always LTR and VIEW_START
1294 mDataView.setTextDirection(View.TEXT_DIRECTION_LTR);
1295 mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001296 }
1297 }
1298
1299 private void setMarqueeText(TextView textView, char[] text, int size) {
1300 if (getTextEllipsis() == TruncateAt.MARQUEE) {
1301 setMarqueeText(textView, new String(text, 0, size));
1302 } else {
1303 textView.setText(text, 0, size);
1304 }
1305 }
1306
1307 private void setMarqueeText(TextView textView, CharSequence text) {
1308 if (getTextEllipsis() == TruncateAt.MARQUEE) {
1309 // To show MARQUEE correctly (with END effect during non-active state), we need
1310 // to build Spanned with MARQUEE in addition to TextView's ellipsize setting.
1311 final SpannableString spannable = new SpannableString(text);
1312 spannable.setSpan(TruncateAt.MARQUEE, 0, spannable.length(),
1313 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1314 textView.setText(spannable);
1315 } else {
1316 textView.setText(text);
1317 }
1318 }
1319
1320 /**
Wenyi Wange4e359f2016-04-02 14:30:02 -07001321 * Returns the {@link AppCompatCheckBox} view, creating it if necessary.
Brian Attwell207a8772015-02-27 16:35:00 -08001322 */
Wenyi Wange4e359f2016-04-02 14:30:02 -07001323 public AppCompatCheckBox getCheckBox() {
Brian Attwell207a8772015-02-27 16:35:00 -08001324 if (mCheckBox == null) {
Wenyi Wange4e359f2016-04-02 14:30:02 -07001325 mCheckBox = new AppCompatCheckBox(getContext());
Brian Attwell207a8772015-02-27 16:35:00 -08001326 // Make non-focusable, so the rest of the ContactListItemView can be clicked.
1327 mCheckBox.setFocusable(false);
1328 addView(mCheckBox);
1329 }
1330 return mCheckBox;
1331 }
1332
1333 /**
Walter Jang379ff852016-05-22 12:17:39 -07001334 * Returns the {@link AppCompatImageButton} delete button, creating it if necessary.
1335 */
Tingting Wang408b0232016-06-28 23:00:30 -07001336 public AppCompatImageButton getDeleteImageButton(
1337 final MultiSelectEntryContactListAdapter.DeleteContactListener listener,
1338 final int position) {
Walter Jang379ff852016-05-22 12:17:39 -07001339 if (mDeleteImageButton == null) {
1340 mDeleteImageButton = new AppCompatImageButton(getContext());
Walter Jang9a6330e2016-06-24 12:19:32 -07001341 mDeleteImageButton.setImageResource(R.drawable.ic_cancel_black_24dp);
Tingting Wang408b0232016-06-28 23:00:30 -07001342 mDeleteImageButton.setScaleType(ScaleType.CENTER);
Walter Jang379ff852016-05-22 12:17:39 -07001343 mDeleteImageButton.setBackgroundColor(Color.TRANSPARENT);
Tingting Wang804e5ce2016-07-13 13:17:55 -07001344 mDeleteImageButton.setContentDescription(
1345 getResources().getString(R.string.description_delete_contact));
guanxiongliuae949932016-07-14 16:25:31 -07001346 if (CompatUtils. isLollipopCompatible()) {
1347 final TypedValue typedValue = new TypedValue();
1348 getContext().getTheme().resolveAttribute(
1349 android.R.attr.selectableItemBackgroundBorderless, typedValue, true);
1350 mDeleteImageButton.setBackgroundResource(typedValue.resourceId);
1351 }
Walter Jang379ff852016-05-22 12:17:39 -07001352 addView(mDeleteImageButton);
1353 }
Tingting Wang408b0232016-06-28 23:00:30 -07001354 // Reset onClickListener because after reloading the view, position might be changed.
1355 mDeleteImageButton.setOnClickListener(new OnClickListener() {
1356 @Override
1357 public void onClick(View v) {
1358 // Inform the adapter that delete icon was clicked.
1359 if (listener != null) {
1360 listener.onContactDeleteClicked(position);
1361 }
1362 }
1363 });
Walter Jang379ff852016-05-22 12:17:39 -07001364 return mDeleteImageButton;
1365 }
1366
1367 /**
Chiao Cheng89437e82012-11-01 13:41:51 -07001368 * Returns the text view for the data text, creating it if necessary.
1369 */
1370 public TextView getDataView() {
1371 if (mDataView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001372 mDataView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001373 mDataView.setSingleLine(true);
1374 mDataView.setEllipsize(getTextEllipsis());
Yorke Lee796824d2013-12-02 15:41:42 -08001375 mDataView.setTextAppearance(getContext(), R.style.TextAppearanceSmall);
Walter Jang0b2f8cf2014-11-12 13:45:19 -08001376 mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001377 mDataView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001378 mDataView.setId(R.id.cliv_data_view);
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001379 if (CompatUtils.isLollipopCompatible()) {
1380 mDataView.setElegantTextHeight(false);
1381 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001382 addView(mDataView);
1383 }
1384 return mDataView;
1385 }
1386
1387 /**
1388 * Adds or updates a text view for the search snippet.
1389 */
1390 public void setSnippet(String text) {
1391 if (TextUtils.isEmpty(text)) {
1392 if (mSnippetView != null) {
1393 mSnippetView.setVisibility(View.GONE);
1394 }
1395 } else {
Christine Chenccba9502013-07-12 12:04:54 -07001396 mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix);
Chiao Cheng89437e82012-11-01 13:41:51 -07001397 mSnippetView.setVisibility(VISIBLE);
Walter Jang1a21fe52014-10-15 18:32:44 -07001398 if (ContactDisplayUtils.isPossiblePhoneNumber(text)) {
1399 // Give the text-to-speech engine a hint that it's a phone number
Wenyi Wang40346982015-11-18 14:58:42 -08001400 mSnippetView.setContentDescription(
1401 PhoneNumberUtilsCompat.createTtsSpannable(text));
Walter Jang1a21fe52014-10-15 18:32:44 -07001402 } else {
1403 mSnippetView.setContentDescription(null);
1404 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001405 }
1406 }
1407
1408 /**
1409 * Returns the text view for the search snippet, creating it if necessary.
1410 */
1411 public TextView getSnippetView() {
1412 if (mSnippetView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001413 mSnippetView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001414 mSnippetView.setSingleLine(true);
1415 mSnippetView.setEllipsize(getTextEllipsis());
Yorke Leef3f259c2013-10-21 15:06:18 -07001416 mSnippetView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
Walter Jang0b2f8cf2014-11-12 13:45:19 -08001417 mSnippetView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001418 mSnippetView.setActivated(isActivated());
1419 addView(mSnippetView);
1420 }
1421 return mSnippetView;
1422 }
1423
1424 /**
1425 * Returns the text view for the status, creating it if necessary.
1426 */
1427 public TextView getStatusView() {
1428 if (mStatusView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001429 mStatusView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001430 mStatusView.setSingleLine(true);
1431 mStatusView.setEllipsize(getTextEllipsis());
Yorke Leef3f259c2013-10-21 15:06:18 -07001432 mStatusView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
Chiao Cheng89437e82012-11-01 13:41:51 -07001433 mStatusView.setTextColor(mSecondaryTextColor);
1434 mStatusView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001435 mStatusView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001436 addView(mStatusView);
1437 }
1438 return mStatusView;
1439 }
1440
1441 /**
Chiao Cheng89437e82012-11-01 13:41:51 -07001442 * Adds or updates a text view for the status.
1443 */
1444 public void setStatus(CharSequence text) {
1445 if (TextUtils.isEmpty(text)) {
1446 if (mStatusView != null) {
1447 mStatusView.setVisibility(View.GONE);
1448 }
1449 } else {
1450 getStatusView();
1451 setMarqueeText(mStatusView, text);
1452 mStatusView.setVisibility(VISIBLE);
1453 }
1454 }
1455
1456 /**
1457 * Adds or updates the presence icon view.
1458 */
1459 public void setPresence(Drawable icon) {
1460 if (icon != null) {
1461 if (mPresenceIcon == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001462 mPresenceIcon = new ImageView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001463 addView(mPresenceIcon);
1464 }
1465 mPresenceIcon.setImageDrawable(icon);
1466 mPresenceIcon.setScaleType(ScaleType.CENTER);
1467 mPresenceIcon.setVisibility(View.VISIBLE);
1468 } else {
1469 if (mPresenceIcon != null) {
1470 mPresenceIcon.setVisibility(View.GONE);
1471 }
1472 }
1473 }
1474
Victor Chang3e21eef2015-12-24 13:17:42 +08001475 /**
1476 * Set to display work profile icon or not
1477 *
1478 * @param enabled set to display work profile icon or not
1479 */
1480 public void setWorkProfileIconEnabled(boolean enabled) {
1481 if (mWorkProfileIcon != null) {
1482 mWorkProfileIcon.setVisibility(enabled ? View.VISIBLE : View.GONE);
1483 } else if (enabled) {
1484 mWorkProfileIcon = new ImageView(getContext());
1485 addView(mWorkProfileIcon);
1486 mWorkProfileIcon.setImageResource(R.drawable.ic_work_profile);
1487 mWorkProfileIcon.setScaleType(ScaleType.CENTER_INSIDE);
1488 mWorkProfileIcon.setVisibility(View.VISIBLE);
1489 }
1490 }
1491
Chiao Cheng89437e82012-11-01 13:41:51 -07001492 private TruncateAt getTextEllipsis() {
1493 return TruncateAt.MARQUEE;
1494 }
1495
1496 public void showDisplayName(Cursor cursor, int nameColumnIndex, int displayOrder) {
1497 CharSequence name = cursor.getString(nameColumnIndex);
Yorke Lee64913842013-08-28 12:14:51 -07001498 setDisplayName(name);
1499
1500 // Since the quick contact content description is derived from the display name and there is
1501 // no guarantee that when the quick contact is initialized the display name is already set,
1502 // do it here too.
1503 if (mQuickContact != null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001504 mQuickContact.setContentDescription(getContext().getString(
Yorke Lee64913842013-08-28 12:14:51 -07001505 R.string.description_quick_contact_for, mNameTextView.getText()));
1506 }
1507 }
1508
1509 public void setDisplayName(CharSequence name, boolean highlight) {
1510 if (!TextUtils.isEmpty(name) && highlight) {
1511 clearHighlightSequences();
1512 addNameHighlightSequence(0, name.length());
1513 }
1514 setDisplayName(name);
1515 }
1516
1517 public void setDisplayName(CharSequence name) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001518 if (!TextUtils.isEmpty(name)) {
Christine Chen3efbe592013-07-08 18:05:03 -07001519 // Chooses the available highlighting method for highlighting.
1520 if (mHighlightedPrefix != null) {
1521 name = mTextHighlighter.applyPrefixHighlight(name, mHighlightedPrefix);
Christine Chenccba9502013-07-12 12:04:54 -07001522 } else if (mNameHighlightSequence.size() != 0) {
1523 final SpannableString spannableName = new SpannableString(name);
1524 for (HighlightSequence highlightSequence : mNameHighlightSequence) {
1525 mTextHighlighter.applyMaskingHighlight(spannableName, highlightSequence.start,
1526 highlightSequence.end);
1527 }
1528 name = spannableName;
Christine Chen3efbe592013-07-08 18:05:03 -07001529 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001530 } else {
1531 name = mUnknownNameText;
1532 }
1533 setMarqueeText(getNameTextView(), name);
Walter Jang1a21fe52014-10-15 18:32:44 -07001534
1535 if (ContactDisplayUtils.isPossiblePhoneNumber(name)) {
1536 // Give the text-to-speech engine a hint that it's a phone number
Tingting Wang6bb619d2015-11-05 17:47:33 -08001537 mNameTextView.setTextDirection(View.TEXT_DIRECTION_LTR);
Walter Jang1a21fe52014-10-15 18:32:44 -07001538 mNameTextView.setContentDescription(
Wenyi Wang40346982015-11-18 14:58:42 -08001539 PhoneNumberUtilsCompat.createTtsSpannable(name.toString()));
Walter Jang1a21fe52014-10-15 18:32:44 -07001540 } else {
Qi Wang8a2f9f62016-03-03 14:05:51 -08001541 // Remove span tags of highlighting for talkback to avoid reading highlighting and rest
1542 // of the name into two separate parts.
1543 mNameTextView.setContentDescription(name.toString());
Walter Jang1a21fe52014-10-15 18:32:44 -07001544 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001545 }
1546
Brian Attwell207a8772015-02-27 16:35:00 -08001547 public void hideCheckBox() {
1548 if (mCheckBox != null) {
1549 removeView(mCheckBox);
1550 mCheckBox = null;
1551 }
1552 }
1553
Walter Jang379ff852016-05-22 12:17:39 -07001554 public void hideDeleteImageButton() {
1555 if (mDeleteImageButton != null) {
1556 removeView(mDeleteImageButton);
1557 mDeleteImageButton = null;
1558 }
1559 }
1560
Chiao Cheng89437e82012-11-01 13:41:51 -07001561 public void hideDisplayName() {
1562 if (mNameTextView != null) {
1563 removeView(mNameTextView);
1564 mNameTextView = null;
1565 }
1566 }
1567
1568 public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
1569 cursor.copyStringToBuffer(phoneticNameColumnIndex, mPhoneticNameBuffer);
1570 int phoneticNameSize = mPhoneticNameBuffer.sizeCopied;
1571 if (phoneticNameSize != 0) {
1572 setPhoneticName(mPhoneticNameBuffer.data, phoneticNameSize);
1573 } else {
1574 setPhoneticName(null, 0);
1575 }
1576 }
1577
1578 public void hidePhoneticName() {
1579 if (mPhoneticNameTextView != null) {
1580 removeView(mPhoneticNameTextView);
1581 mPhoneticNameTextView = null;
1582 }
1583 }
1584
1585 /**
1586 * Sets the proper icon (star or presence or nothing) and/or status message.
1587 */
1588 public void showPresenceAndStatusMessage(Cursor cursor, int presenceColumnIndex,
1589 int contactStatusColumnIndex) {
1590 Drawable icon = null;
1591 int presence = 0;
1592 if (!cursor.isNull(presenceColumnIndex)) {
1593 presence = cursor.getInt(presenceColumnIndex);
1594 icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), presence);
1595 }
1596 setPresence(icon);
1597
1598 String statusMessage = null;
1599 if (contactStatusColumnIndex != 0 && !cursor.isNull(contactStatusColumnIndex)) {
1600 statusMessage = cursor.getString(contactStatusColumnIndex);
1601 }
1602 // If there is no status message from the contact, but there was a presence value, then use
1603 // the default status message string
1604 if (statusMessage == null && presence != 0) {
1605 statusMessage = ContactStatusUtil.getStatusString(getContext(), presence);
1606 }
1607 setStatus(statusMessage);
1608 }
1609
1610 /**
Walter Jangbad5e4b2016-02-13 15:50:21 -08001611 * Shows search snippet for email and phone number matches.
1612 */
1613 public void showSnippet(Cursor cursor, String query, int snippetColumn) {
1614 // TODO: this does not properly handle phone numbers with control characters
1615 // For example if the phone number is 444-5555, the search query 4445 will match the
1616 // number since we normalize it before querying CP2 but the snippet will fail since
1617 // the portion to be highlighted is 444-5 not 4445.
1618 final String snippet = cursor.getString(snippetColumn);
1619 if (snippet == null) {
1620 setSnippet(null);
1621 return;
1622 }
1623 final String displayName = cursor.getColumnIndex(Contacts.DISPLAY_NAME) >= 0
1624 ? cursor.getString(cursor.getColumnIndex(Contacts.DISPLAY_NAME)) : null;
1625 if (snippet.equals(displayName)) {
1626 // If the snippet exactly matches the display name (i.e. the phone number or email
1627 // address is being used as the display name) then no snippet is necessary
1628 setSnippet(null);
1629 return;
1630 }
1631 // Show the snippet with the part of the query that matched it
1632 setSnippet(updateSnippet(snippet, query, displayName));
1633 }
1634
1635 /**
Chiao Cheng89437e82012-11-01 13:41:51 -07001636 * Shows search snippet.
1637 */
1638 public void showSnippet(Cursor cursor, int summarySnippetColumnIndex) {
Walter Jang54564402016-01-18 11:56:19 -08001639 if (cursor.getColumnCount() <= summarySnippetColumnIndex
1640 || !SearchSnippets.SNIPPET.equals(cursor.getColumnName(summarySnippetColumnIndex))) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001641 setSnippet(null);
1642 return;
1643 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001644
Chiao Cheng901c5e52012-12-19 16:06:37 -08001645 String snippet = cursor.getString(summarySnippetColumnIndex);
Chiao Chengecba27e2012-12-27 17:14:53 -08001646
Chiao Cheng89437e82012-11-01 13:41:51 -07001647 // Do client side snippeting if provider didn't do it
Chiao Cheng901c5e52012-12-19 16:06:37 -08001648 final Bundle extras = cursor.getExtras();
Chiao Cheng89437e82012-11-01 13:41:51 -07001649 if (extras.getBoolean(ContactsContract.DEFERRED_SNIPPETING)) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001650
Chiao Cheng901c5e52012-12-19 16:06:37 -08001651 final String query = extras.getString(ContactsContract.DEFERRED_SNIPPETING_QUERY);
Chiao Cheng89437e82012-11-01 13:41:51 -07001652
Chiao Chengecba27e2012-12-27 17:14:53 -08001653 String displayName = null;
1654 int displayNameIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
1655 if (displayNameIndex >= 0) {
1656 displayName = cursor.getString(displayNameIndex);
Chiao Cheng901c5e52012-12-19 16:06:37 -08001657 }
Chiao Chengecba27e2012-12-27 17:14:53 -08001658
1659 snippet = updateSnippet(snippet, query, displayName);
1660
Chiao Cheng901c5e52012-12-19 16:06:37 -08001661 } else {
Chiao Cheng901c5e52012-12-19 16:06:37 -08001662 if (snippet != null) {
1663 int from = 0;
1664 int to = snippet.length();
1665 int start = snippet.indexOf(DefaultContactListAdapter.SNIPPET_START_MATCH);
1666 if (start == -1) {
1667 snippet = null;
1668 } else {
1669 int firstNl = snippet.lastIndexOf('\n', start);
1670 if (firstNl != -1) {
1671 from = firstNl + 1;
1672 }
1673 int end = snippet.lastIndexOf(DefaultContactListAdapter.SNIPPET_END_MATCH);
1674 if (end != -1) {
1675 int lastNl = snippet.indexOf('\n', end);
1676 if (lastNl != -1) {
1677 to = lastNl;
1678 }
1679 }
1680
1681 StringBuilder sb = new StringBuilder();
1682 for (int i = from; i < to; i++) {
1683 char c = snippet.charAt(i);
1684 if (c != DefaultContactListAdapter.SNIPPET_START_MATCH &&
1685 c != DefaultContactListAdapter.SNIPPET_END_MATCH) {
1686 sb.append(c);
1687 }
1688 }
1689 snippet = sb.toString();
1690 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001691 }
1692 }
Chiao Chengecba27e2012-12-27 17:14:53 -08001693
Chiao Cheng89437e82012-11-01 13:41:51 -07001694 setSnippet(snippet);
1695 }
1696
Chiao Chengecba27e2012-12-27 17:14:53 -08001697 /**
1698 * Used for deferred snippets from the database. The contents come back as large strings which
1699 * need to be extracted for display.
1700 *
1701 * @param snippet The snippet from the database.
1702 * @param query The search query substring.
1703 * @param displayName The contact display name.
1704 * @return The proper snippet to display.
1705 */
1706 private String updateSnippet(String snippet, String query, String displayName) {
1707
1708 if (TextUtils.isEmpty(snippet) || TextUtils.isEmpty(query)) {
1709 return null;
1710 }
1711 query = SearchUtil.cleanStartAndEndOfSearchQuery(query.toLowerCase());
1712
1713 // If the display name already contains the query term, return empty - snippets should
1714 // not be needed in that case.
1715 if (!TextUtils.isEmpty(displayName)) {
1716 final String lowerDisplayName = displayName.toLowerCase();
1717 final List<String> nameTokens = split(lowerDisplayName);
1718 for (String nameToken : nameTokens) {
1719 if (nameToken.startsWith(query)) {
1720 return null;
1721 }
1722 }
1723 }
1724
1725 // The snippet may contain multiple data lines.
1726 // Show the first line that matches the query.
1727 final SearchUtil.MatchedLine matched = SearchUtil.findMatchingLine(snippet, query);
1728
1729 if (matched != null && matched.line != null) {
1730 // Tokenize for long strings since the match may be at the end of it.
1731 // Skip this part for short strings since the whole string will be displayed.
1732 // Most contact strings are short so the snippetize method will be called infrequently.
1733 final int lengthThreshold = getResources().getInteger(
1734 R.integer.snippet_length_before_tokenize);
1735 if (matched.line.length() > lengthThreshold) {
1736 return snippetize(matched.line, matched.startIndex, lengthThreshold);
1737 } else {
1738 return matched.line;
1739 }
1740 }
1741
1742 // No match found.
1743 return null;
1744 }
1745
1746 private String snippetize(String line, int matchIndex, int maxLength) {
1747 // Show up to maxLength characters. But we only show full tokens so show the last full token
1748 // up to maxLength characters. So as many starting tokens as possible before trying ending
1749 // tokens.
1750 int remainingLength = maxLength;
1751 int tempRemainingLength = remainingLength;
1752
1753 // Start the end token after the matched query.
1754 int index = matchIndex;
1755 int endTokenIndex = index;
1756
1757 // Find the match token first.
1758 while (index < line.length()) {
1759 if (!Character.isLetterOrDigit(line.charAt(index))) {
1760 endTokenIndex = index;
1761 remainingLength = tempRemainingLength;
1762 break;
1763 }
1764 tempRemainingLength--;
1765 index++;
1766 }
1767
1768 // Find as much content before the match.
1769 index = matchIndex - 1;
1770 tempRemainingLength = remainingLength;
1771 int startTokenIndex = matchIndex;
1772 while (index > -1 && tempRemainingLength > 0) {
1773 if (!Character.isLetterOrDigit(line.charAt(index))) {
1774 startTokenIndex = index;
1775 remainingLength = tempRemainingLength;
1776 }
1777 tempRemainingLength--;
1778 index--;
1779 }
1780
1781 index = endTokenIndex;
1782 tempRemainingLength = remainingLength;
1783 // Find remaining content at after match.
1784 while (index < line.length() && tempRemainingLength > 0) {
1785 if (!Character.isLetterOrDigit(line.charAt(index))) {
1786 endTokenIndex = index;
1787 }
1788 tempRemainingLength--;
1789 index++;
1790 }
1791 // Append ellipse if there is content before or after.
1792 final StringBuilder sb = new StringBuilder();
1793 if (startTokenIndex > 0) {
1794 sb.append("...");
1795 }
1796 sb.append(line.substring(startTokenIndex, endTokenIndex));
1797 if (endTokenIndex < line.length()) {
1798 sb.append("...");
1799 }
1800 return sb.toString();
1801 }
1802
Chiao Cheng901c5e52012-12-19 16:06:37 -08001803 private static final Pattern SPLIT_PATTERN = Pattern.compile(
1804 "([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
1805
1806 /**
1807 * Helper method for splitting a string into tokens. The lists passed in are populated with
1808 * the
1809 * tokens and offsets into the content of each token. The tokenization function parses e-mail
1810 * addresses as a single token; otherwise it splits on any non-alphanumeric character.
1811 *
1812 * @param content Content to split.
1813 * @return List of token strings.
1814 */
1815 private static List<String> split(String content) {
1816 final Matcher matcher = SPLIT_PATTERN.matcher(content);
1817 final ArrayList<String> tokens = Lists.newArrayList();
1818 while (matcher.find()) {
1819 tokens.add(matcher.group());
1820 }
1821 return tokens;
1822 }
1823
Chiao Cheng89437e82012-11-01 13:41:51 -07001824 /**
Jay Shrauner138515a2013-07-26 09:32:27 -07001825 * Shows data element.
Chiao Cheng89437e82012-11-01 13:41:51 -07001826 */
1827 public void showData(Cursor cursor, int dataColumnIndex) {
1828 cursor.copyStringToBuffer(dataColumnIndex, mDataBuffer);
Jay Shrauner138515a2013-07-26 09:32:27 -07001829 setData(mDataBuffer.data, mDataBuffer.sizeCopied);
1830 }
1831
Chiao Cheng89437e82012-11-01 13:41:51 -07001832 public void setActivatedStateSupported(boolean flag) {
1833 this.mActivatedStateSupported = flag;
1834 }
1835
Andrew Lee4683e542014-06-09 16:24:10 -07001836 public void setAdjustSelectionBoundsEnabled(boolean enabled) {
1837 mAdjustSelectionBoundsEnabled = enabled;
1838 }
1839
Chiao Cheng89437e82012-11-01 13:41:51 -07001840 @Override
1841 public void requestLayout() {
1842 // We will assume that once measured this will not need to resize
1843 // itself, so there is no need to pass the layout request to the parent
1844 // view (ListView).
1845 forceLayout();
1846 }
1847
1848 public void setPhotoPosition(PhotoPosition photoPosition) {
1849 mPhotoPosition = photoPosition;
1850 }
1851
1852 public PhotoPosition getPhotoPosition() {
1853 return mPhotoPosition;
1854 }
1855
1856 /**
Andrew Leedddf3562015-03-20 17:57:47 -07001857 * Set drawable resources directly for the drawable resource of the photo view.
Yorke Lee64913842013-08-28 12:14:51 -07001858 *
Andrew Leedddf3562015-03-20 17:57:47 -07001859 * @param drawableId Id of drawable resource.
Yorke Lee64913842013-08-28 12:14:51 -07001860 */
Andrew Leedddf3562015-03-20 17:57:47 -07001861 public void setDrawableResource(int drawableId) {
1862 ImageView photo = getPhotoView();
Yorke Lee64913842013-08-28 12:14:51 -07001863 photo.setScaleType(ImageView.ScaleType.CENTER);
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001864 final Drawable drawable = ContextCompat.getDrawable(getContext(), drawableId);
1865 final int iconColor =
1866 ContextCompat.getColor(getContext(), R.color.search_shortcut_icon_color);
1867 if (CompatUtils.isLollipopCompatible()) {
1868 photo.setImageDrawable(drawable);
1869 photo.setImageTintList(ColorStateList.valueOf(iconColor));
1870 } else {
1871 final Drawable drawableWrapper = DrawableCompat.wrap(drawable).mutate();
1872 DrawableCompat.setTint(drawableWrapper, iconColor);
1873 photo.setImageDrawable(drawableWrapper);
1874 }
Yorke Lee64913842013-08-28 12:14:51 -07001875 }
Yorke Lee1c171fa2013-09-06 13:53:49 -07001876
1877 @Override
1878 public boolean onTouchEvent(MotionEvent event) {
1879 final float x = event.getX();
1880 final float y = event.getY();
Yorke Lee68ec6a12013-11-13 14:12:08 -08001881 // If the touch event's coordinates are not within the view's header, then delegate
1882 // to super.onTouchEvent so that regular view behavior is preserved. Otherwise, consume
1883 // and ignore the touch event.
Yorke Lee62ff5cd2013-12-03 13:55:34 -08001884 if (mBoundsWithoutHeader.contains((int) x, (int) y) || !pointIsInView(x, y)) {
Yorke Lee1c171fa2013-09-06 13:53:49 -07001885 return super.onTouchEvent(event);
1886 } else {
1887 return true;
1888 }
1889 }
Yorke Lee62ff5cd2013-12-03 13:55:34 -08001890
1891 private final boolean pointIsInView(float localX, float localY) {
Sai Cheemalapatid814b032014-06-30 14:46:25 -07001892 return localX >= mLeftOffset && localX < mRightOffset
Yorke Lee3174f562014-03-07 12:58:58 -08001893 && localY >= 0 && localY < (getBottom() - getTop());
Yorke Lee62ff5cd2013-12-03 13:55:34 -08001894 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001895}