blob: 4f42a0f690a18098a40add463696c5edf9b2681a [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
Gary Mai69c182a2016-12-05 13:07:03 -080017package com.android.contacts.list;
Chiao Cheng89437e82012-11-01 13:41:51 -070018
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
Gary Mai0a49afa2016-12-05 15:53:58 -080054import com.android.contacts.ContactPresenceIconUtil;
55import com.android.contacts.ContactStatusUtil;
Arthur Wang3f6a2442016-12-05 14:51:59 -080056import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080057import com.android.contacts.compat.CompatUtils;
58import com.android.contacts.compat.PhoneNumberUtilsCompat;
59import com.android.contacts.format.TextHighlighter;
60import com.android.contacts.util.ContactDisplayUtils;
61import com.android.contacts.util.SearchUtil;
62import com.android.contacts.util.ViewUtil;
Yorke Lee50a89a52013-11-04 14:44:30 -080063
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));
John Shaobd9ef3c2016-12-15 12:42:03 -0800397 mVideoCallIcon.setImageResource(R.drawable.quantum_ic_videocam_vd_theme_24);
Tyler Gunn001d9742015-12-18 13:57:02 -0800398 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(
John Shaobd9ef3c2016-12-15 12:42:03 -08001034 getResources().getDrawable(R.drawable.quantum_ic_star_vd_theme_24,
1035 getContext().getTheme()));
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001036 headerImageView.setImageTintList(ColorStateList.valueOf(getResources()
1037 .getColor(R.color.material_star_pink)));
1038 headerImageView.setContentDescription(
Wenyi Wang7438b132016-11-17 15:39:24 -08001039 getContext().getString(R.string.contactsFavoritesLabel));
Wenyi Wangd5403fa2016-07-20 17:16:38 -07001040 headerImageView.setVisibility(View.VISIBLE);
1041 addView(headerImageView);
1042 }
1043
Andrew Lee020ba622014-04-25 15:26:35 -07001044 public void setIsSectionHeaderEnabled(boolean isSectionHeaderEnabled) {
1045 mIsSectionHeaderEnabled = isSectionHeaderEnabled;
1046 }
1047
Chiao Cheng89437e82012-11-01 13:41:51 -07001048 /**
1049 * Returns the quick contact badge, creating it if necessary.
1050 */
1051 public QuickContactBadge getQuickContact() {
1052 if (!mQuickContactEnabled) {
1053 throw new IllegalStateException("QuickContact is disabled for this view");
1054 }
1055 if (mQuickContact == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001056 mQuickContact = new QuickContactBadge(getContext());
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001057 if (CompatUtils.isLollipopCompatible()) {
1058 mQuickContact.setOverlay(null);
1059 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001060 mQuickContact.setLayoutParams(getDefaultPhotoLayoutParams());
1061 if (mNameTextView != null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001062 mQuickContact.setContentDescription(getContext().getString(
Chiao Cheng89437e82012-11-01 13:41:51 -07001063 R.string.description_quick_contact_for, mNameTextView.getText()));
1064 }
1065
1066 addView(mQuickContact);
1067 mPhotoViewWidthAndHeightAreReady = false;
1068 }
1069 return mQuickContact;
1070 }
1071
1072 /**
1073 * Returns the photo view, creating it if necessary.
1074 */
1075 public ImageView getPhotoView() {
1076 if (mPhotoView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001077 mPhotoView = new ImageView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001078 mPhotoView.setLayoutParams(getDefaultPhotoLayoutParams());
1079 // Quick contact style used above will set a background - remove it
1080 mPhotoView.setBackground(null);
1081 addView(mPhotoView);
1082 mPhotoViewWidthAndHeightAreReady = false;
1083 }
1084 return mPhotoView;
1085 }
1086
1087 /**
1088 * Removes the photo view.
1089 */
1090 public void removePhotoView() {
1091 removePhotoView(false, true);
1092 }
1093
1094 /**
1095 * Removes the photo view.
1096 *
1097 * @param keepHorizontalPadding True means data on the right side will have
1098 * padding on left, pretending there is still a photo view.
1099 * @param keepVerticalPadding True means the View will have some height
1100 * enough for accommodating a photo view.
1101 */
1102 public void removePhotoView(boolean keepHorizontalPadding, boolean keepVerticalPadding) {
1103 mPhotoViewWidthAndHeightAreReady = false;
1104 mKeepHorizontalPaddingForPhotoView = keepHorizontalPadding;
1105 mKeepVerticalPaddingForPhotoView = keepVerticalPadding;
1106 if (mPhotoView != null) {
1107 removeView(mPhotoView);
1108 mPhotoView = null;
1109 }
1110 if (mQuickContact != null) {
1111 removeView(mQuickContact);
1112 mQuickContact = null;
1113 }
1114 }
1115
1116 /**
1117 * Sets a word prefix that will be highlighted if encountered in fields like
Christine Chen3efbe592013-07-08 18:05:03 -07001118 * name and search snippet. This will disable the mask highlighting for names.
Chiao Cheng89437e82012-11-01 13:41:51 -07001119 * <p>
1120 * NOTE: must be all upper-case
1121 */
Chiao Chenga1554ef2012-12-21 15:45:54 -08001122 public void setHighlightedPrefix(String upperCasePrefix) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001123 mHighlightedPrefix = upperCasePrefix;
Christine Chen3efbe592013-07-08 18:05:03 -07001124 }
1125
1126 /**
Christine Chenccba9502013-07-12 12:04:54 -07001127 * Clears previously set highlight sequences for the view.
Christine Chen3efbe592013-07-08 18:05:03 -07001128 */
Christine Chenccba9502013-07-12 12:04:54 -07001129 public void clearHighlightSequences() {
1130 mNameHighlightSequence.clear();
1131 mNumberHighlightSequence.clear();
Christine Chen3efbe592013-07-08 18:05:03 -07001132 mHighlightedPrefix = null;
1133 }
1134
1135 /**
Christine Chenccba9502013-07-12 12:04:54 -07001136 * Adds a highlight sequence to the name highlighter.
1137 * @param start The start position of the highlight sequence.
1138 * @param end The end position of the highlight sequence.
Christine Chen3efbe592013-07-08 18:05:03 -07001139 */
Christine Chenccba9502013-07-12 12:04:54 -07001140 public void addNameHighlightSequence(int start, int end) {
1141 mNameHighlightSequence.add(new HighlightSequence(start, end));
1142 }
1143
1144 /**
1145 * Adds a highlight sequence to the number highlighter.
1146 * @param start The start position of the highlight sequence.
1147 * @param end The end position of the highlight sequence.
1148 */
1149 public void addNumberHighlightSequence(int start, int end) {
1150 mNumberHighlightSequence.add(new HighlightSequence(start, end));
Chiao Cheng89437e82012-11-01 13:41:51 -07001151 }
1152
1153 /**
1154 * Returns the text view for the contact name, creating it if necessary.
1155 */
1156 public TextView getNameTextView() {
1157 if (mNameTextView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001158 mNameTextView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001159 mNameTextView.setSingleLine(true);
1160 mNameTextView.setEllipsize(getTextEllipsis());
Marcus Hagerott2aa31982016-10-25 14:36:25 -07001161 mNameTextView.setTextColor(ResourcesCompat.getColorStateList(getResources(),
1162 R.color.contact_list_name_text_color, getContext().getTheme()));
Wenyi Wangbaf26222016-07-11 22:24:41 -07001163 mNameTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mNameTextViewTextSize);
Chiao Cheng89437e82012-11-01 13:41:51 -07001164 // Manually call setActivated() since this view may be added after the first
1165 // setActivated() call toward this whole item view.
1166 mNameTextView.setActivated(isActivated());
1167 mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001168 mNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
1169 mNameTextView.setId(R.id.cliv_name_textview);
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001170 if (CompatUtils.isLollipopCompatible()) {
1171 mNameTextView.setElegantTextHeight(false);
1172 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001173 addView(mNameTextView);
1174 }
1175 return mNameTextView;
1176 }
1177
1178 /**
1179 * Adds or updates a text view for the phonetic name.
1180 */
1181 public void setPhoneticName(char[] text, int size) {
1182 if (text == null || size == 0) {
1183 if (mPhoneticNameTextView != null) {
1184 mPhoneticNameTextView.setVisibility(View.GONE);
1185 }
1186 } else {
1187 getPhoneticNameTextView();
1188 setMarqueeText(mPhoneticNameTextView, text, size);
1189 mPhoneticNameTextView.setVisibility(VISIBLE);
1190 }
1191 }
1192
1193 /**
1194 * Returns the text view for the phonetic name, creating it if necessary.
1195 */
1196 public TextView getPhoneticNameTextView() {
1197 if (mPhoneticNameTextView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001198 mPhoneticNameTextView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001199 mPhoneticNameTextView.setSingleLine(true);
1200 mPhoneticNameTextView.setEllipsize(getTextEllipsis());
Yorke Leef3f259c2013-10-21 15:06:18 -07001201 mPhoneticNameTextView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
Walter Jang0b2f8cf2014-11-12 13:45:19 -08001202 mPhoneticNameTextView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001203 mPhoneticNameTextView.setTypeface(mPhoneticNameTextView.getTypeface(), Typeface.BOLD);
1204 mPhoneticNameTextView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001205 mPhoneticNameTextView.setId(R.id.cliv_phoneticname_textview);
Chiao Cheng89437e82012-11-01 13:41:51 -07001206 addView(mPhoneticNameTextView);
1207 }
1208 return mPhoneticNameTextView;
1209 }
1210
1211 /**
1212 * Adds or updates a text view for the data label.
1213 */
1214 public void setLabel(CharSequence text) {
1215 if (TextUtils.isEmpty(text)) {
1216 if (mLabelView != null) {
1217 mLabelView.setVisibility(View.GONE);
1218 }
1219 } else {
1220 getLabelView();
1221 setMarqueeText(mLabelView, text);
1222 mLabelView.setVisibility(VISIBLE);
1223 }
1224 }
1225
1226 /**
1227 * Returns the text view for the data label, creating it if necessary.
1228 */
1229 public TextView getLabelView() {
1230 if (mLabelView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001231 mLabelView = new TextView(getContext());
Tyler Gunn001d9742015-12-18 13:57:02 -08001232 mLabelView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
1233 LayoutParams.WRAP_CONTENT));
1234
Chiao Cheng89437e82012-11-01 13:41:51 -07001235 mLabelView.setSingleLine(true);
1236 mLabelView.setEllipsize(getTextEllipsis());
Yorke Lee796824d2013-12-02 15:41:42 -08001237 mLabelView.setTextAppearance(getContext(), R.style.TextAppearanceSmall);
Chiao Cheng89437e82012-11-01 13:41:51 -07001238 if (mPhotoPosition == PhotoPosition.LEFT) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001239 mLabelView.setAllCaps(true);
Chiao Cheng89437e82012-11-01 13:41:51 -07001240 } else {
1241 mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
1242 }
1243 mLabelView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001244 mLabelView.setId(R.id.cliv_label_textview);
Chiao Cheng89437e82012-11-01 13:41:51 -07001245 addView(mLabelView);
1246 }
1247 return mLabelView;
1248 }
1249
1250 /**
1251 * Adds or updates a text view for the data element.
1252 */
Christine Chen3efbe592013-07-08 18:05:03 -07001253 public void setData(char[] text, int size) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001254 if (text == null || size == 0) {
1255 if (mDataView != null) {
1256 mDataView.setVisibility(View.GONE);
1257 }
1258 } else {
1259 getDataView();
1260 setMarqueeText(mDataView, text, size);
1261 mDataView.setVisibility(VISIBLE);
Christine Chen3efbe592013-07-08 18:05:03 -07001262 }
1263 }
1264
1265 /**
1266 * Sets phone number for a list item. This takes care of number highlighting if the highlight
1267 * mask exists.
1268 */
Andrew Lee32e17da2014-03-31 12:44:34 -07001269 public void setPhoneNumber(String text, String countryIso) {
Christine Chen3efbe592013-07-08 18:05:03 -07001270 if (text == null) {
1271 if (mDataView != null) {
1272 mDataView.setVisibility(View.GONE);
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001273 }
Christine Chen3efbe592013-07-08 18:05:03 -07001274 } else {
1275 getDataView();
Andrew Lee32e17da2014-03-31 12:44:34 -07001276
Yorke Lee69f471a2014-04-08 15:00:47 -07001277 // TODO: Format number using PhoneNumberUtils.formatNumber before assigning it to
1278 // mDataView. Make sure that determination of the highlight sequences are done only
1279 // after number formatting.
Andrew Lee32e17da2014-03-31 12:44:34 -07001280
Christine Chen3efbe592013-07-08 18:05:03 -07001281 // Sets phone number texts for display after highlighting it, if applicable.
Andrew Lee32e17da2014-03-31 12:44:34 -07001282 // CharSequence textToSet = text;
Christine Chenccba9502013-07-12 12:04:54 -07001283 final SpannableString textToSet = new SpannableString(text);
1284
1285 if (mNumberHighlightSequence.size() != 0) {
1286 final HighlightSequence highlightSequence = mNumberHighlightSequence.get(0);
1287 mTextHighlighter.applyMaskingHighlight(textToSet, highlightSequence.start,
1288 highlightSequence.end);
Christine Chen3efbe592013-07-08 18:05:03 -07001289 }
Christine Chenccba9502013-07-12 12:04:54 -07001290
Christine Chen3efbe592013-07-08 18:05:03 -07001291 setMarqueeText(mDataView, textToSet);
1292 mDataView.setVisibility(VISIBLE);
1293
1294 // We have a phone number as "mDataView" so make it always LTR and VIEW_START
1295 mDataView.setTextDirection(View.TEXT_DIRECTION_LTR);
1296 mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001297 }
1298 }
1299
1300 private void setMarqueeText(TextView textView, char[] text, int size) {
1301 if (getTextEllipsis() == TruncateAt.MARQUEE) {
1302 setMarqueeText(textView, new String(text, 0, size));
1303 } else {
1304 textView.setText(text, 0, size);
1305 }
1306 }
1307
1308 private void setMarqueeText(TextView textView, CharSequence text) {
1309 if (getTextEllipsis() == TruncateAt.MARQUEE) {
1310 // To show MARQUEE correctly (with END effect during non-active state), we need
1311 // to build Spanned with MARQUEE in addition to TextView's ellipsize setting.
1312 final SpannableString spannable = new SpannableString(text);
1313 spannable.setSpan(TruncateAt.MARQUEE, 0, spannable.length(),
1314 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1315 textView.setText(spannable);
1316 } else {
1317 textView.setText(text);
1318 }
1319 }
1320
1321 /**
Wenyi Wange4e359f2016-04-02 14:30:02 -07001322 * Returns the {@link AppCompatCheckBox} view, creating it if necessary.
Brian Attwell207a8772015-02-27 16:35:00 -08001323 */
Wenyi Wange4e359f2016-04-02 14:30:02 -07001324 public AppCompatCheckBox getCheckBox() {
Brian Attwell207a8772015-02-27 16:35:00 -08001325 if (mCheckBox == null) {
Wenyi Wange4e359f2016-04-02 14:30:02 -07001326 mCheckBox = new AppCompatCheckBox(getContext());
Brian Attwell207a8772015-02-27 16:35:00 -08001327 // Make non-focusable, so the rest of the ContactListItemView can be clicked.
1328 mCheckBox.setFocusable(false);
1329 addView(mCheckBox);
1330 }
1331 return mCheckBox;
1332 }
1333
1334 /**
Walter Jang379ff852016-05-22 12:17:39 -07001335 * Returns the {@link AppCompatImageButton} delete button, creating it if necessary.
1336 */
Tingting Wang408b0232016-06-28 23:00:30 -07001337 public AppCompatImageButton getDeleteImageButton(
1338 final MultiSelectEntryContactListAdapter.DeleteContactListener listener,
1339 final int position) {
Walter Jang379ff852016-05-22 12:17:39 -07001340 if (mDeleteImageButton == null) {
1341 mDeleteImageButton = new AppCompatImageButton(getContext());
John Shaobd9ef3c2016-12-15 12:42:03 -08001342 mDeleteImageButton.setImageResource(R.drawable.quantum_ic_cancel_vd_theme_24);
Tingting Wang408b0232016-06-28 23:00:30 -07001343 mDeleteImageButton.setScaleType(ScaleType.CENTER);
Walter Jang379ff852016-05-22 12:17:39 -07001344 mDeleteImageButton.setBackgroundColor(Color.TRANSPARENT);
Tingting Wang804e5ce2016-07-13 13:17:55 -07001345 mDeleteImageButton.setContentDescription(
1346 getResources().getString(R.string.description_delete_contact));
guanxiongliuae949932016-07-14 16:25:31 -07001347 if (CompatUtils. isLollipopCompatible()) {
1348 final TypedValue typedValue = new TypedValue();
1349 getContext().getTheme().resolveAttribute(
1350 android.R.attr.selectableItemBackgroundBorderless, typedValue, true);
1351 mDeleteImageButton.setBackgroundResource(typedValue.resourceId);
1352 }
Walter Jang379ff852016-05-22 12:17:39 -07001353 addView(mDeleteImageButton);
1354 }
Tingting Wang408b0232016-06-28 23:00:30 -07001355 // Reset onClickListener because after reloading the view, position might be changed.
1356 mDeleteImageButton.setOnClickListener(new OnClickListener() {
1357 @Override
1358 public void onClick(View v) {
1359 // Inform the adapter that delete icon was clicked.
1360 if (listener != null) {
1361 listener.onContactDeleteClicked(position);
1362 }
1363 }
1364 });
Walter Jang379ff852016-05-22 12:17:39 -07001365 return mDeleteImageButton;
1366 }
1367
1368 /**
Chiao Cheng89437e82012-11-01 13:41:51 -07001369 * Returns the text view for the data text, creating it if necessary.
1370 */
1371 public TextView getDataView() {
1372 if (mDataView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001373 mDataView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001374 mDataView.setSingleLine(true);
1375 mDataView.setEllipsize(getTextEllipsis());
Yorke Lee796824d2013-12-02 15:41:42 -08001376 mDataView.setTextAppearance(getContext(), R.style.TextAppearanceSmall);
Walter Jang0b2f8cf2014-11-12 13:45:19 -08001377 mDataView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001378 mDataView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001379 mDataView.setId(R.id.cliv_data_view);
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001380 if (CompatUtils.isLollipopCompatible()) {
1381 mDataView.setElegantTextHeight(false);
1382 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001383 addView(mDataView);
1384 }
1385 return mDataView;
1386 }
1387
1388 /**
1389 * Adds or updates a text view for the search snippet.
1390 */
1391 public void setSnippet(String text) {
1392 if (TextUtils.isEmpty(text)) {
1393 if (mSnippetView != null) {
1394 mSnippetView.setVisibility(View.GONE);
1395 }
1396 } else {
Christine Chenccba9502013-07-12 12:04:54 -07001397 mTextHighlighter.setPrefixText(getSnippetView(), text, mHighlightedPrefix);
Chiao Cheng89437e82012-11-01 13:41:51 -07001398 mSnippetView.setVisibility(VISIBLE);
Walter Jang1a21fe52014-10-15 18:32:44 -07001399 if (ContactDisplayUtils.isPossiblePhoneNumber(text)) {
1400 // Give the text-to-speech engine a hint that it's a phone number
Wenyi Wang40346982015-11-18 14:58:42 -08001401 mSnippetView.setContentDescription(
1402 PhoneNumberUtilsCompat.createTtsSpannable(text));
Walter Jang1a21fe52014-10-15 18:32:44 -07001403 } else {
1404 mSnippetView.setContentDescription(null);
1405 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001406 }
1407 }
1408
1409 /**
1410 * Returns the text view for the search snippet, creating it if necessary.
1411 */
1412 public TextView getSnippetView() {
1413 if (mSnippetView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001414 mSnippetView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001415 mSnippetView.setSingleLine(true);
1416 mSnippetView.setEllipsize(getTextEllipsis());
Yorke Leef3f259c2013-10-21 15:06:18 -07001417 mSnippetView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
Walter Jang0b2f8cf2014-11-12 13:45:19 -08001418 mSnippetView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001419 mSnippetView.setActivated(isActivated());
1420 addView(mSnippetView);
1421 }
1422 return mSnippetView;
1423 }
1424
1425 /**
1426 * Returns the text view for the status, creating it if necessary.
1427 */
1428 public TextView getStatusView() {
1429 if (mStatusView == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001430 mStatusView = new TextView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001431 mStatusView.setSingleLine(true);
1432 mStatusView.setEllipsize(getTextEllipsis());
Yorke Leef3f259c2013-10-21 15:06:18 -07001433 mStatusView.setTextAppearance(getContext(), android.R.style.TextAppearance_Small);
Chiao Cheng89437e82012-11-01 13:41:51 -07001434 mStatusView.setTextColor(mSecondaryTextColor);
1435 mStatusView.setActivated(isActivated());
Fabrice Di Meglio29a5cf92013-04-03 20:59:09 -07001436 mStatusView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
Chiao Cheng89437e82012-11-01 13:41:51 -07001437 addView(mStatusView);
1438 }
1439 return mStatusView;
1440 }
1441
1442 /**
Chiao Cheng89437e82012-11-01 13:41:51 -07001443 * Adds or updates a text view for the status.
1444 */
1445 public void setStatus(CharSequence text) {
1446 if (TextUtils.isEmpty(text)) {
1447 if (mStatusView != null) {
1448 mStatusView.setVisibility(View.GONE);
1449 }
1450 } else {
1451 getStatusView();
1452 setMarqueeText(mStatusView, text);
1453 mStatusView.setVisibility(VISIBLE);
1454 }
1455 }
1456
1457 /**
1458 * Adds or updates the presence icon view.
1459 */
1460 public void setPresence(Drawable icon) {
1461 if (icon != null) {
1462 if (mPresenceIcon == null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001463 mPresenceIcon = new ImageView(getContext());
Chiao Cheng89437e82012-11-01 13:41:51 -07001464 addView(mPresenceIcon);
1465 }
1466 mPresenceIcon.setImageDrawable(icon);
1467 mPresenceIcon.setScaleType(ScaleType.CENTER);
1468 mPresenceIcon.setVisibility(View.VISIBLE);
1469 } else {
1470 if (mPresenceIcon != null) {
1471 mPresenceIcon.setVisibility(View.GONE);
1472 }
1473 }
1474 }
1475
Victor Chang3e21eef2015-12-24 13:17:42 +08001476 /**
1477 * Set to display work profile icon or not
1478 *
1479 * @param enabled set to display work profile icon or not
1480 */
1481 public void setWorkProfileIconEnabled(boolean enabled) {
1482 if (mWorkProfileIcon != null) {
1483 mWorkProfileIcon.setVisibility(enabled ? View.VISIBLE : View.GONE);
1484 } else if (enabled) {
1485 mWorkProfileIcon = new ImageView(getContext());
1486 addView(mWorkProfileIcon);
1487 mWorkProfileIcon.setImageResource(R.drawable.ic_work_profile);
1488 mWorkProfileIcon.setScaleType(ScaleType.CENTER_INSIDE);
1489 mWorkProfileIcon.setVisibility(View.VISIBLE);
1490 }
1491 }
1492
Chiao Cheng89437e82012-11-01 13:41:51 -07001493 private TruncateAt getTextEllipsis() {
1494 return TruncateAt.MARQUEE;
1495 }
1496
1497 public void showDisplayName(Cursor cursor, int nameColumnIndex, int displayOrder) {
1498 CharSequence name = cursor.getString(nameColumnIndex);
Yorke Lee64913842013-08-28 12:14:51 -07001499 setDisplayName(name);
1500
1501 // Since the quick contact content description is derived from the display name and there is
1502 // no guarantee that when the quick contact is initialized the display name is already set,
1503 // do it here too.
1504 if (mQuickContact != null) {
Yorke Leef3f259c2013-10-21 15:06:18 -07001505 mQuickContact.setContentDescription(getContext().getString(
Yorke Lee64913842013-08-28 12:14:51 -07001506 R.string.description_quick_contact_for, mNameTextView.getText()));
1507 }
1508 }
1509
1510 public void setDisplayName(CharSequence name, boolean highlight) {
1511 if (!TextUtils.isEmpty(name) && highlight) {
1512 clearHighlightSequences();
1513 addNameHighlightSequence(0, name.length());
1514 }
1515 setDisplayName(name);
1516 }
1517
1518 public void setDisplayName(CharSequence name) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001519 if (!TextUtils.isEmpty(name)) {
Christine Chen3efbe592013-07-08 18:05:03 -07001520 // Chooses the available highlighting method for highlighting.
1521 if (mHighlightedPrefix != null) {
1522 name = mTextHighlighter.applyPrefixHighlight(name, mHighlightedPrefix);
Christine Chenccba9502013-07-12 12:04:54 -07001523 } else if (mNameHighlightSequence.size() != 0) {
1524 final SpannableString spannableName = new SpannableString(name);
1525 for (HighlightSequence highlightSequence : mNameHighlightSequence) {
1526 mTextHighlighter.applyMaskingHighlight(spannableName, highlightSequence.start,
1527 highlightSequence.end);
1528 }
1529 name = spannableName;
Christine Chen3efbe592013-07-08 18:05:03 -07001530 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001531 } else {
1532 name = mUnknownNameText;
1533 }
1534 setMarqueeText(getNameTextView(), name);
Walter Jang1a21fe52014-10-15 18:32:44 -07001535
1536 if (ContactDisplayUtils.isPossiblePhoneNumber(name)) {
1537 // Give the text-to-speech engine a hint that it's a phone number
Tingting Wang6bb619d2015-11-05 17:47:33 -08001538 mNameTextView.setTextDirection(View.TEXT_DIRECTION_LTR);
Walter Jang1a21fe52014-10-15 18:32:44 -07001539 mNameTextView.setContentDescription(
Wenyi Wang40346982015-11-18 14:58:42 -08001540 PhoneNumberUtilsCompat.createTtsSpannable(name.toString()));
Walter Jang1a21fe52014-10-15 18:32:44 -07001541 } else {
Qi Wang8a2f9f62016-03-03 14:05:51 -08001542 // Remove span tags of highlighting for talkback to avoid reading highlighting and rest
1543 // of the name into two separate parts.
1544 mNameTextView.setContentDescription(name.toString());
Walter Jang1a21fe52014-10-15 18:32:44 -07001545 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001546 }
1547
Brian Attwell207a8772015-02-27 16:35:00 -08001548 public void hideCheckBox() {
1549 if (mCheckBox != null) {
1550 removeView(mCheckBox);
1551 mCheckBox = null;
1552 }
1553 }
1554
Walter Jang379ff852016-05-22 12:17:39 -07001555 public void hideDeleteImageButton() {
1556 if (mDeleteImageButton != null) {
1557 removeView(mDeleteImageButton);
1558 mDeleteImageButton = null;
1559 }
1560 }
1561
Chiao Cheng89437e82012-11-01 13:41:51 -07001562 public void hideDisplayName() {
1563 if (mNameTextView != null) {
1564 removeView(mNameTextView);
1565 mNameTextView = null;
1566 }
1567 }
1568
1569 public void showPhoneticName(Cursor cursor, int phoneticNameColumnIndex) {
1570 cursor.copyStringToBuffer(phoneticNameColumnIndex, mPhoneticNameBuffer);
1571 int phoneticNameSize = mPhoneticNameBuffer.sizeCopied;
1572 if (phoneticNameSize != 0) {
1573 setPhoneticName(mPhoneticNameBuffer.data, phoneticNameSize);
1574 } else {
1575 setPhoneticName(null, 0);
1576 }
1577 }
1578
1579 public void hidePhoneticName() {
1580 if (mPhoneticNameTextView != null) {
1581 removeView(mPhoneticNameTextView);
1582 mPhoneticNameTextView = null;
1583 }
1584 }
1585
1586 /**
1587 * Sets the proper icon (star or presence or nothing) and/or status message.
1588 */
1589 public void showPresenceAndStatusMessage(Cursor cursor, int presenceColumnIndex,
1590 int contactStatusColumnIndex) {
1591 Drawable icon = null;
1592 int presence = 0;
1593 if (!cursor.isNull(presenceColumnIndex)) {
1594 presence = cursor.getInt(presenceColumnIndex);
1595 icon = ContactPresenceIconUtil.getPresenceIcon(getContext(), presence);
1596 }
1597 setPresence(icon);
1598
1599 String statusMessage = null;
1600 if (contactStatusColumnIndex != 0 && !cursor.isNull(contactStatusColumnIndex)) {
1601 statusMessage = cursor.getString(contactStatusColumnIndex);
1602 }
1603 // If there is no status message from the contact, but there was a presence value, then use
1604 // the default status message string
1605 if (statusMessage == null && presence != 0) {
1606 statusMessage = ContactStatusUtil.getStatusString(getContext(), presence);
1607 }
1608 setStatus(statusMessage);
1609 }
1610
1611 /**
Walter Jangbad5e4b2016-02-13 15:50:21 -08001612 * Shows search snippet for email and phone number matches.
1613 */
1614 public void showSnippet(Cursor cursor, String query, int snippetColumn) {
1615 // TODO: this does not properly handle phone numbers with control characters
1616 // For example if the phone number is 444-5555, the search query 4445 will match the
1617 // number since we normalize it before querying CP2 but the snippet will fail since
1618 // the portion to be highlighted is 444-5 not 4445.
1619 final String snippet = cursor.getString(snippetColumn);
1620 if (snippet == null) {
1621 setSnippet(null);
1622 return;
1623 }
1624 final String displayName = cursor.getColumnIndex(Contacts.DISPLAY_NAME) >= 0
1625 ? cursor.getString(cursor.getColumnIndex(Contacts.DISPLAY_NAME)) : null;
1626 if (snippet.equals(displayName)) {
1627 // If the snippet exactly matches the display name (i.e. the phone number or email
1628 // address is being used as the display name) then no snippet is necessary
1629 setSnippet(null);
1630 return;
1631 }
1632 // Show the snippet with the part of the query that matched it
1633 setSnippet(updateSnippet(snippet, query, displayName));
1634 }
1635
1636 /**
Chiao Cheng89437e82012-11-01 13:41:51 -07001637 * Shows search snippet.
1638 */
1639 public void showSnippet(Cursor cursor, int summarySnippetColumnIndex) {
Walter Jang54564402016-01-18 11:56:19 -08001640 if (cursor.getColumnCount() <= summarySnippetColumnIndex
1641 || !SearchSnippets.SNIPPET.equals(cursor.getColumnName(summarySnippetColumnIndex))) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001642 setSnippet(null);
1643 return;
1644 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001645
Chiao Cheng901c5e52012-12-19 16:06:37 -08001646 String snippet = cursor.getString(summarySnippetColumnIndex);
Chiao Chengecba27e2012-12-27 17:14:53 -08001647
Chiao Cheng89437e82012-11-01 13:41:51 -07001648 // Do client side snippeting if provider didn't do it
Chiao Cheng901c5e52012-12-19 16:06:37 -08001649 final Bundle extras = cursor.getExtras();
Chiao Cheng89437e82012-11-01 13:41:51 -07001650 if (extras.getBoolean(ContactsContract.DEFERRED_SNIPPETING)) {
Chiao Cheng89437e82012-11-01 13:41:51 -07001651
Chiao Cheng901c5e52012-12-19 16:06:37 -08001652 final String query = extras.getString(ContactsContract.DEFERRED_SNIPPETING_QUERY);
Chiao Cheng89437e82012-11-01 13:41:51 -07001653
Chiao Chengecba27e2012-12-27 17:14:53 -08001654 String displayName = null;
1655 int displayNameIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
1656 if (displayNameIndex >= 0) {
1657 displayName = cursor.getString(displayNameIndex);
Chiao Cheng901c5e52012-12-19 16:06:37 -08001658 }
Chiao Chengecba27e2012-12-27 17:14:53 -08001659
1660 snippet = updateSnippet(snippet, query, displayName);
1661
Chiao Cheng901c5e52012-12-19 16:06:37 -08001662 } else {
Chiao Cheng901c5e52012-12-19 16:06:37 -08001663 if (snippet != null) {
1664 int from = 0;
1665 int to = snippet.length();
1666 int start = snippet.indexOf(DefaultContactListAdapter.SNIPPET_START_MATCH);
1667 if (start == -1) {
1668 snippet = null;
1669 } else {
1670 int firstNl = snippet.lastIndexOf('\n', start);
1671 if (firstNl != -1) {
1672 from = firstNl + 1;
1673 }
1674 int end = snippet.lastIndexOf(DefaultContactListAdapter.SNIPPET_END_MATCH);
1675 if (end != -1) {
1676 int lastNl = snippet.indexOf('\n', end);
1677 if (lastNl != -1) {
1678 to = lastNl;
1679 }
1680 }
1681
1682 StringBuilder sb = new StringBuilder();
1683 for (int i = from; i < to; i++) {
1684 char c = snippet.charAt(i);
1685 if (c != DefaultContactListAdapter.SNIPPET_START_MATCH &&
1686 c != DefaultContactListAdapter.SNIPPET_END_MATCH) {
1687 sb.append(c);
1688 }
1689 }
1690 snippet = sb.toString();
1691 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001692 }
1693 }
Chiao Chengecba27e2012-12-27 17:14:53 -08001694
Chiao Cheng89437e82012-11-01 13:41:51 -07001695 setSnippet(snippet);
1696 }
1697
Chiao Chengecba27e2012-12-27 17:14:53 -08001698 /**
1699 * Used for deferred snippets from the database. The contents come back as large strings which
1700 * need to be extracted for display.
1701 *
1702 * @param snippet The snippet from the database.
1703 * @param query The search query substring.
1704 * @param displayName The contact display name.
1705 * @return The proper snippet to display.
1706 */
1707 private String updateSnippet(String snippet, String query, String displayName) {
1708
1709 if (TextUtils.isEmpty(snippet) || TextUtils.isEmpty(query)) {
1710 return null;
1711 }
1712 query = SearchUtil.cleanStartAndEndOfSearchQuery(query.toLowerCase());
1713
1714 // If the display name already contains the query term, return empty - snippets should
1715 // not be needed in that case.
1716 if (!TextUtils.isEmpty(displayName)) {
1717 final String lowerDisplayName = displayName.toLowerCase();
1718 final List<String> nameTokens = split(lowerDisplayName);
1719 for (String nameToken : nameTokens) {
1720 if (nameToken.startsWith(query)) {
1721 return null;
1722 }
1723 }
1724 }
1725
1726 // The snippet may contain multiple data lines.
1727 // Show the first line that matches the query.
1728 final SearchUtil.MatchedLine matched = SearchUtil.findMatchingLine(snippet, query);
1729
1730 if (matched != null && matched.line != null) {
1731 // Tokenize for long strings since the match may be at the end of it.
1732 // Skip this part for short strings since the whole string will be displayed.
1733 // Most contact strings are short so the snippetize method will be called infrequently.
1734 final int lengthThreshold = getResources().getInteger(
1735 R.integer.snippet_length_before_tokenize);
1736 if (matched.line.length() > lengthThreshold) {
1737 return snippetize(matched.line, matched.startIndex, lengthThreshold);
1738 } else {
1739 return matched.line;
1740 }
1741 }
1742
1743 // No match found.
1744 return null;
1745 }
1746
1747 private String snippetize(String line, int matchIndex, int maxLength) {
1748 // Show up to maxLength characters. But we only show full tokens so show the last full token
1749 // up to maxLength characters. So as many starting tokens as possible before trying ending
1750 // tokens.
1751 int remainingLength = maxLength;
1752 int tempRemainingLength = remainingLength;
1753
1754 // Start the end token after the matched query.
1755 int index = matchIndex;
1756 int endTokenIndex = index;
1757
1758 // Find the match token first.
1759 while (index < line.length()) {
1760 if (!Character.isLetterOrDigit(line.charAt(index))) {
1761 endTokenIndex = index;
1762 remainingLength = tempRemainingLength;
1763 break;
1764 }
1765 tempRemainingLength--;
1766 index++;
1767 }
1768
1769 // Find as much content before the match.
1770 index = matchIndex - 1;
1771 tempRemainingLength = remainingLength;
1772 int startTokenIndex = matchIndex;
1773 while (index > -1 && tempRemainingLength > 0) {
1774 if (!Character.isLetterOrDigit(line.charAt(index))) {
1775 startTokenIndex = index;
1776 remainingLength = tempRemainingLength;
1777 }
1778 tempRemainingLength--;
1779 index--;
1780 }
1781
1782 index = endTokenIndex;
1783 tempRemainingLength = remainingLength;
1784 // Find remaining content at after match.
1785 while (index < line.length() && tempRemainingLength > 0) {
1786 if (!Character.isLetterOrDigit(line.charAt(index))) {
1787 endTokenIndex = index;
1788 }
1789 tempRemainingLength--;
1790 index++;
1791 }
1792 // Append ellipse if there is content before or after.
1793 final StringBuilder sb = new StringBuilder();
1794 if (startTokenIndex > 0) {
1795 sb.append("...");
1796 }
1797 sb.append(line.substring(startTokenIndex, endTokenIndex));
1798 if (endTokenIndex < line.length()) {
1799 sb.append("...");
1800 }
1801 return sb.toString();
1802 }
1803
Chiao Cheng901c5e52012-12-19 16:06:37 -08001804 private static final Pattern SPLIT_PATTERN = Pattern.compile(
1805 "([\\w-\\.]+)@((?:[\\w]+\\.)+)([a-zA-Z]{2,4})|[\\w]+");
1806
1807 /**
1808 * Helper method for splitting a string into tokens. The lists passed in are populated with
1809 * the
1810 * tokens and offsets into the content of each token. The tokenization function parses e-mail
1811 * addresses as a single token; otherwise it splits on any non-alphanumeric character.
1812 *
1813 * @param content Content to split.
1814 * @return List of token strings.
1815 */
1816 private static List<String> split(String content) {
1817 final Matcher matcher = SPLIT_PATTERN.matcher(content);
1818 final ArrayList<String> tokens = Lists.newArrayList();
1819 while (matcher.find()) {
1820 tokens.add(matcher.group());
1821 }
1822 return tokens;
1823 }
1824
Chiao Cheng89437e82012-11-01 13:41:51 -07001825 /**
Jay Shrauner138515a2013-07-26 09:32:27 -07001826 * Shows data element.
Chiao Cheng89437e82012-11-01 13:41:51 -07001827 */
1828 public void showData(Cursor cursor, int dataColumnIndex) {
1829 cursor.copyStringToBuffer(dataColumnIndex, mDataBuffer);
Jay Shrauner138515a2013-07-26 09:32:27 -07001830 setData(mDataBuffer.data, mDataBuffer.sizeCopied);
1831 }
1832
Chiao Cheng89437e82012-11-01 13:41:51 -07001833 public void setActivatedStateSupported(boolean flag) {
1834 this.mActivatedStateSupported = flag;
1835 }
1836
Andrew Lee4683e542014-06-09 16:24:10 -07001837 public void setAdjustSelectionBoundsEnabled(boolean enabled) {
1838 mAdjustSelectionBoundsEnabled = enabled;
1839 }
1840
Chiao Cheng89437e82012-11-01 13:41:51 -07001841 @Override
1842 public void requestLayout() {
1843 // We will assume that once measured this will not need to resize
1844 // itself, so there is no need to pass the layout request to the parent
1845 // view (ListView).
1846 forceLayout();
1847 }
1848
1849 public void setPhotoPosition(PhotoPosition photoPosition) {
1850 mPhotoPosition = photoPosition;
1851 }
1852
1853 public PhotoPosition getPhotoPosition() {
1854 return mPhotoPosition;
1855 }
1856
1857 /**
Andrew Leedddf3562015-03-20 17:57:47 -07001858 * Set drawable resources directly for the drawable resource of the photo view.
Yorke Lee64913842013-08-28 12:14:51 -07001859 *
Andrew Leedddf3562015-03-20 17:57:47 -07001860 * @param drawableId Id of drawable resource.
Yorke Lee64913842013-08-28 12:14:51 -07001861 */
Andrew Leedddf3562015-03-20 17:57:47 -07001862 public void setDrawableResource(int drawableId) {
1863 ImageView photo = getPhotoView();
Yorke Lee64913842013-08-28 12:14:51 -07001864 photo.setScaleType(ImageView.ScaleType.CENTER);
Wenyi Wang54ea4b12015-12-16 14:18:59 -08001865 final Drawable drawable = ContextCompat.getDrawable(getContext(), drawableId);
1866 final int iconColor =
1867 ContextCompat.getColor(getContext(), R.color.search_shortcut_icon_color);
1868 if (CompatUtils.isLollipopCompatible()) {
1869 photo.setImageDrawable(drawable);
1870 photo.setImageTintList(ColorStateList.valueOf(iconColor));
1871 } else {
1872 final Drawable drawableWrapper = DrawableCompat.wrap(drawable).mutate();
1873 DrawableCompat.setTint(drawableWrapper, iconColor);
1874 photo.setImageDrawable(drawableWrapper);
1875 }
Yorke Lee64913842013-08-28 12:14:51 -07001876 }
Yorke Lee1c171fa2013-09-06 13:53:49 -07001877
1878 @Override
1879 public boolean onTouchEvent(MotionEvent event) {
1880 final float x = event.getX();
1881 final float y = event.getY();
Yorke Lee68ec6a12013-11-13 14:12:08 -08001882 // If the touch event's coordinates are not within the view's header, then delegate
1883 // to super.onTouchEvent so that regular view behavior is preserved. Otherwise, consume
1884 // and ignore the touch event.
Yorke Lee62ff5cd2013-12-03 13:55:34 -08001885 if (mBoundsWithoutHeader.contains((int) x, (int) y) || !pointIsInView(x, y)) {
Yorke Lee1c171fa2013-09-06 13:53:49 -07001886 return super.onTouchEvent(event);
1887 } else {
1888 return true;
1889 }
1890 }
Yorke Lee62ff5cd2013-12-03 13:55:34 -08001891
1892 private final boolean pointIsInView(float localX, float localY) {
Sai Cheemalapatid814b032014-06-30 14:46:25 -07001893 return localX >= mLeftOffset && localX < mRightOffset
Yorke Lee3174f562014-03-07 12:58:58 -08001894 && localY >= 0 && localY < (getBottom() - getTop());
Yorke Lee62ff5cd2013-12-03 13:55:34 -08001895 }
Chiao Cheng89437e82012-11-01 13:41:51 -07001896}