• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.contacts;
18 
19 import com.android.contacts.ui.widget.DontPressWithParentImageView;
20 
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.content.res.TypedArray;
24 import android.graphics.Canvas;
25 import android.graphics.Typeface;
26 import android.graphics.drawable.Drawable;
27 import android.provider.ContactsContract.Contacts;
28 import android.text.TextUtils;
29 import android.text.TextUtils.TruncateAt;
30 import android.util.AttributeSet;
31 import android.view.Gravity;
32 import android.view.View;
33 import android.view.ViewGroup;
34 import android.widget.ImageView;
35 import android.widget.QuickContactBadge;
36 import android.widget.TextView;
37 import android.widget.ImageView.ScaleType;
38 
39 /**
40  * A custom view for an item in the contact list.
41  */
42 public class ContactListItemView extends ViewGroup {
43 
44     private static final int QUICK_CONTACT_BADGE_STYLE =
45             com.android.internal.R.attr.quickContactBadgeStyleWindowMedium;
46 
47     private final Context mContext;
48 
49     private final int mPreferredHeight;
50     private final int mVerticalDividerMargin;
51     private final int mPaddingTop;
52     private final int mPaddingRight;
53     private final int mPaddingBottom;
54     private final int mPaddingLeft;
55     private final int mGapBetweenImageAndText;
56     private final int mGapBetweenLabelAndData;
57     private final int mCallButtonPadding;
58     private final int mPresenceIconMargin;
59     private final int mHeaderTextWidth;
60 
61     private boolean mHorizontalDividerVisible;
62     private Drawable mHorizontalDividerDrawable;
63     private int mHorizontalDividerHeight;
64 
65     private boolean mVerticalDividerVisible;
66     private Drawable mVerticalDividerDrawable;
67     private int mVerticalDividerWidth;
68 
69     private boolean mHeaderVisible;
70     private Drawable mHeaderBackgroundDrawable;
71     private int mHeaderBackgroundHeight;
72     private TextView mHeaderTextView;
73 
74     private QuickContactBadge mQuickContact;
75     private ImageView mPhotoView;
76     private TextView mNameTextView;
77     private DontPressWithParentImageView mCallButton;
78     private TextView mLabelView;
79     private TextView mDataView;
80     private TextView mSnippetView;
81     private ImageView mPresenceIcon;
82 
83     private int mPhotoViewWidth;
84     private int mPhotoViewHeight;
85     private int mLine1Height;
86     private int mLine2Height;
87     private int mLine3Height;
88 
89     private OnClickListener mCallButtonClickListener;
90 
ContactListItemView(Context context, AttributeSet attrs)91     public ContactListItemView(Context context, AttributeSet attrs) {
92         super(context, attrs);
93         mContext = context;
94 
95         // Obtain preferred item height from the current theme
96         TypedArray a = context.obtainStyledAttributes(null, com.android.internal.R.styleable.Theme);
97         mPreferredHeight =
98                 a.getDimensionPixelSize(android.R.styleable.Theme_listPreferredItemHeight, 0);
99         a.recycle();
100 
101         Resources resources = context.getResources();
102         mVerticalDividerMargin =
103                 resources.getDimensionPixelOffset(R.dimen.list_item_vertical_divider_margin);
104         mPaddingTop =
105                 resources.getDimensionPixelOffset(R.dimen.list_item_padding_top);
106         mPaddingBottom =
107                 resources.getDimensionPixelOffset(R.dimen.list_item_padding_bottom);
108         mPaddingLeft =
109                 resources.getDimensionPixelOffset(R.dimen.list_item_padding_left);
110         mPaddingRight =
111                 resources.getDimensionPixelOffset(R.dimen.list_item_padding_right);
112         mGapBetweenImageAndText =
113                 resources.getDimensionPixelOffset(R.dimen.list_item_gap_between_image_and_text);
114         mGapBetweenLabelAndData =
115                 resources.getDimensionPixelOffset(R.dimen.list_item_gap_between_label_and_data);
116         mCallButtonPadding =
117                 resources.getDimensionPixelOffset(R.dimen.list_item_call_button_padding);
118         mPresenceIconMargin =
119                 resources.getDimensionPixelOffset(R.dimen.list_item_presence_icon_margin);
120         mHeaderTextWidth =
121                 resources.getDimensionPixelOffset(R.dimen.list_item_header_text_width);
122     }
123 
124     /**
125      * Installs a call button listener.
126      */
setOnCallButtonClickListener(OnClickListener callButtonClickListener)127     public void setOnCallButtonClickListener(OnClickListener callButtonClickListener) {
128         mCallButtonClickListener = callButtonClickListener;
129     }
130 
131     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)132     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
133         // We will match parent's width and wrap content vertically, but make sure
134         // height is no less than listPreferredItemHeight.
135         int width = resolveSize(0, widthMeasureSpec);
136         int height = 0;
137 
138         mLine1Height = 0;
139         mLine2Height = 0;
140         mLine3Height = 0;
141 
142         // Obtain the natural dimensions of the name text (we only care about height)
143         mNameTextView.measure(0, 0);
144 
145         mLine1Height = mNameTextView.getMeasuredHeight();
146 
147         if (isVisible(mLabelView)) {
148             mLabelView.measure(0, 0);
149             mLine2Height = mLabelView.getMeasuredHeight();
150         }
151 
152         if (isVisible(mDataView)) {
153             mDataView.measure(0, 0);
154             mLine2Height = Math.max(mLine2Height, mDataView.getMeasuredHeight());
155         }
156 
157         if (isVisible(mSnippetView)) {
158             mSnippetView.measure(0, 0);
159             mLine3Height = mSnippetView.getMeasuredHeight();
160         }
161 
162         height += mLine1Height + mLine2Height + mLine3Height;
163 
164         if (isVisible(mCallButton)) {
165             mCallButton.measure(0, 0);
166         }
167         if (isVisible(mPresenceIcon)) {
168             mPresenceIcon.measure(0, 0);
169         }
170 
171         ensurePhotoViewSize();
172 
173         height = Math.max(height, mPhotoViewHeight);
174         height = Math.max(height, mPreferredHeight);
175 
176         if (mHeaderVisible) {
177             ensureHeaderBackground();
178             mHeaderTextView.measure(
179                     MeasureSpec.makeMeasureSpec(mHeaderTextWidth, MeasureSpec.EXACTLY),
180                     MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
181             height += mHeaderBackgroundDrawable.getIntrinsicHeight();
182         }
183 
184         setMeasuredDimension(width, height);
185     }
186 
187     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)188     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
189         int height = bottom - top;
190         int width = right - left;
191 
192         // Determine the vertical bounds by laying out the header first.
193         int topBound = 0;
194 
195         if (mHeaderVisible) {
196             mHeaderBackgroundDrawable.setBounds(
197                     0,
198                     0,
199                     width,
200                     mHeaderBackgroundHeight);
201             mHeaderTextView.layout(0, 0, width, mHeaderBackgroundHeight);
202             topBound += mHeaderBackgroundHeight;
203         }
204 
205         // Positions of views on the left are fixed and so are those on the right side.
206         // The stretchable part of the layout is in the middle.  So, we will start off
207         // by laying out the left and right sides. Then we will allocate the remainder
208         // to the text fields in the middle.
209 
210         // Left side
211         int leftBound = mPaddingLeft;
212         View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
213         if (photoView != null) {
214             // Center the photo vertically
215             int photoTop = topBound + (height - topBound - mPhotoViewHeight) / 2;
216             photoView.layout(
217                     leftBound,
218                     photoTop,
219                     leftBound + mPhotoViewWidth,
220                     photoTop + mPhotoViewHeight);
221             leftBound += mPhotoViewWidth + mGapBetweenImageAndText;
222         }
223 
224         // Right side
225         int rightBound = right;
226         if (isVisible(mCallButton)) {
227             int buttonWidth = mCallButton.getMeasuredWidth();
228             rightBound -= buttonWidth;
229             mCallButton.layout(
230                     rightBound,
231                     topBound,
232                     rightBound + buttonWidth,
233                     height);
234             mVerticalDividerVisible = true;
235             ensureVerticalDivider();
236             rightBound -= mVerticalDividerWidth;
237             mVerticalDividerDrawable.setBounds(
238                     rightBound,
239                     topBound + mVerticalDividerMargin,
240                     rightBound + mVerticalDividerWidth,
241                     height - mVerticalDividerMargin);
242         } else {
243             mVerticalDividerVisible = false;
244         }
245 
246         if (isVisible(mPresenceIcon)) {
247             int iconWidth = mPresenceIcon.getMeasuredWidth();
248             rightBound -= mPresenceIconMargin + iconWidth;
249             mPresenceIcon.layout(
250                     rightBound,
251                     topBound,
252                     rightBound + iconWidth,
253                     height);
254         }
255 
256         if (mHorizontalDividerVisible) {
257             ensureHorizontalDivider();
258             mHorizontalDividerDrawable.setBounds(
259                     0,
260                     height - mHorizontalDividerHeight,
261                     width,
262                     height);
263         }
264 
265         topBound += mPaddingTop;
266         int bottomBound = height - mPaddingBottom;
267 
268         // Text lines, centered vertically
269         rightBound -= mPaddingRight;
270 
271         // Center text vertically
272         int totalTextHeight = mLine1Height + mLine2Height + mLine3Height;
273         int textTopBound = (bottomBound + topBound - totalTextHeight) / 2;
274 
275         mNameTextView.layout(leftBound,
276                 textTopBound,
277                 rightBound,
278                 textTopBound + mLine1Height);
279 
280         int dataLeftBound = leftBound;
281         if (isVisible(mLabelView)) {
282             dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
283             mLabelView.layout(leftBound,
284                     textTopBound + mLine1Height,
285                     dataLeftBound,
286                     textTopBound + mLine1Height + mLine2Height);
287             dataLeftBound += mGapBetweenLabelAndData;
288         }
289 
290         if (isVisible(mDataView)) {
291             mDataView.layout(dataLeftBound,
292                     textTopBound + mLine1Height,
293                     rightBound,
294                     textTopBound + mLine1Height + mLine2Height);
295         }
296 
297         if (isVisible(mSnippetView)) {
298             mSnippetView.layout(leftBound,
299                     textTopBound + mLine1Height + mLine2Height,
300                     rightBound,
301                     textTopBound + mLine1Height + mLine2Height + mLine3Height);
302         }
303     }
304 
isVisible(View view)305     private boolean isVisible(View view) {
306         return view != null && view.getVisibility() == View.VISIBLE;
307     }
308 
309     /**
310      * Loads the drawable for the vertical divider if it has not yet been loaded.
311      */
ensureVerticalDivider()312     private void ensureVerticalDivider() {
313         if (mVerticalDividerDrawable == null) {
314             mVerticalDividerDrawable = mContext.getResources().getDrawable(
315                     R.drawable.divider_vertical_dark);
316             mVerticalDividerWidth = mVerticalDividerDrawable.getIntrinsicWidth();
317         }
318     }
319 
320     /**
321      * Loads the drawable for the horizontal divider if it has not yet been loaded.
322      */
ensureHorizontalDivider()323     private void ensureHorizontalDivider() {
324         if (mHorizontalDividerDrawable == null) {
325             mHorizontalDividerDrawable = mContext.getResources().getDrawable(
326                     com.android.internal.R.drawable.divider_horizontal_dark_opaque);
327             mHorizontalDividerHeight = mHorizontalDividerDrawable.getIntrinsicHeight();
328         }
329     }
330 
331     /**
332      * Loads the drawable for the header background if it has not yet been loaded.
333      */
ensureHeaderBackground()334     private void ensureHeaderBackground() {
335         if (mHeaderBackgroundDrawable == null) {
336             mHeaderBackgroundDrawable = mContext.getResources().getDrawable(
337                     android.R.drawable.dark_header);
338             mHeaderBackgroundHeight = mHeaderBackgroundDrawable.getIntrinsicHeight();
339         }
340     }
341 
342     /**
343      * Extracts width and height from the style
344      */
ensurePhotoViewSize()345     private void ensurePhotoViewSize() {
346         if (mPhotoViewWidth == 0 && mPhotoViewHeight == 0) {
347             TypedArray a = mContext.obtainStyledAttributes(null,
348                     com.android.internal.R.styleable.ViewGroup_Layout,
349                     QUICK_CONTACT_BADGE_STYLE, 0);
350             mPhotoViewWidth = a.getLayoutDimension(
351                     android.R.styleable.ViewGroup_Layout_layout_width,
352                     ViewGroup.LayoutParams.WRAP_CONTENT);
353             mPhotoViewHeight = a.getLayoutDimension(
354                     android.R.styleable.ViewGroup_Layout_layout_height,
355                     ViewGroup.LayoutParams.WRAP_CONTENT);
356             a.recycle();
357         }
358     }
359 
360     @Override
dispatchDraw(Canvas canvas)361     public void dispatchDraw(Canvas canvas) {
362         if (mHeaderVisible) {
363             mHeaderBackgroundDrawable.draw(canvas);
364         }
365         if (mHorizontalDividerVisible) {
366             mHorizontalDividerDrawable.draw(canvas);
367         }
368         if (mVerticalDividerVisible) {
369             mVerticalDividerDrawable.draw(canvas);
370         }
371         super.dispatchDraw(canvas);
372     }
373 
374     /**
375      * Sets the flag that determines whether a divider should drawn at the bottom
376      * of the view.
377      */
setDividerVisible(boolean visible)378     public void setDividerVisible(boolean visible) {
379         mHorizontalDividerVisible = visible;
380     }
381 
382     /**
383      * Sets section header or makes it invisible if the title is null.
384      */
setSectionHeader(String title)385     public void setSectionHeader(String title) {
386         if (!TextUtils.isEmpty(title)) {
387             if (mHeaderTextView == null) {
388                 mHeaderTextView = new TextView(mContext);
389                 mHeaderTextView.setTypeface(mHeaderTextView.getTypeface(), Typeface.BOLD);
390                 mHeaderTextView.setTextColor(mContext.getResources()
391                         .getColor(com.android.internal.R.color.dim_foreground_dark));
392                 mHeaderTextView.setTextSize(14);
393                 mHeaderTextView.setGravity(Gravity.CENTER);
394                 addView(mHeaderTextView);
395             }
396             mHeaderTextView.setText(title);
397             mHeaderTextView.setVisibility(View.VISIBLE);
398             mHeaderVisible = true;
399         } else {
400             if (mHeaderTextView != null) {
401                 mHeaderTextView.setVisibility(View.GONE);
402             }
403             mHeaderVisible = false;
404         }
405     }
406 
407     /**
408      * Returns the quick contact badge, creating it if necessary.
409      */
getQuickContact()410     public QuickContactBadge getQuickContact() {
411         if (mQuickContact == null) {
412             mQuickContact = new QuickContactBadge(mContext, null, QUICK_CONTACT_BADGE_STYLE);
413             mQuickContact.setExcludeMimes(new String[] { Contacts.CONTENT_ITEM_TYPE });
414             addView(mQuickContact);
415         }
416         return mQuickContact;
417     }
418 
419     /**
420      * Returns the photo view, creating it if necessary.
421      */
getPhotoView()422     public ImageView getPhotoView() {
423         if (mPhotoView == null) {
424             mPhotoView = new ImageView(mContext, null, QUICK_CONTACT_BADGE_STYLE);
425             // Quick contact style used above will set a background - remove it
426             mPhotoView.setBackgroundDrawable(null);
427             addView(mPhotoView);
428         }
429         return mPhotoView;
430     }
431 
432     /**
433      * Returns the text view for the contact name, creating it if necessary.
434      */
getNameTextView()435     public TextView getNameTextView() {
436         if (mNameTextView == null) {
437             mNameTextView = new TextView(mContext);
438             mNameTextView.setSingleLine(true);
439             mNameTextView.setEllipsize(TruncateAt.MARQUEE);
440             mNameTextView.setTextAppearance(mContext, android.R.style.TextAppearance_Large);
441             mNameTextView.setGravity(Gravity.CENTER_VERTICAL);
442             addView(mNameTextView);
443         }
444         return mNameTextView;
445     }
446 
447     /**
448      * Adds a call button using the supplied arguments as an id and tag.
449      */
showCallButton(int id, int tag)450     public void showCallButton(int id, int tag) {
451         if (mCallButton == null) {
452             mCallButton = new DontPressWithParentImageView(mContext, null);
453             mCallButton.setId(id);
454             mCallButton.setOnClickListener(mCallButtonClickListener);
455             mCallButton.setBackgroundResource(R.drawable.call_background);
456             mCallButton.setImageResource(android.R.drawable.sym_action_call);
457             mCallButton.setPadding(mCallButtonPadding, 0, mCallButtonPadding, 0);
458             mCallButton.setScaleType(ScaleType.CENTER);
459             addView(mCallButton);
460         }
461 
462         mCallButton.setTag(tag);
463         mCallButton.setVisibility(View.VISIBLE);
464     }
465 
hideCallButton()466     public void hideCallButton() {
467         if (mCallButton != null) {
468             mCallButton.setVisibility(View.GONE);
469         }
470     }
471 
472     /**
473      * Adds or updates a text view for the data label.
474      */
setLabel(CharSequence text)475     public void setLabel(CharSequence text) {
476         if (TextUtils.isEmpty(text)) {
477             if (mLabelView != null) {
478                 mLabelView.setVisibility(View.GONE);
479             }
480         } else {
481             getLabelView();
482             mLabelView.setText(text);
483             mLabelView.setVisibility(VISIBLE);
484         }
485     }
486 
487     /**
488      * Adds or updates a text view for the data label.
489      */
setLabel(char[] text, int size)490     public void setLabel(char[] text, int size) {
491         if (text == null || size == 0) {
492             if (mLabelView != null) {
493                 mLabelView.setVisibility(View.GONE);
494             }
495         } else {
496             getLabelView();
497             mLabelView.setText(text, 0, size);
498             mLabelView.setVisibility(VISIBLE);
499         }
500     }
501 
502     /**
503      * Returns the text view for the data label, creating it if necessary.
504      */
getLabelView()505     public TextView getLabelView() {
506         if (mLabelView == null) {
507             mLabelView = new TextView(mContext);
508             mLabelView.setSingleLine(true);
509             mLabelView.setEllipsize(TruncateAt.MARQUEE);
510             mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
511             mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
512             addView(mLabelView);
513         }
514         return mLabelView;
515     }
516 
517     /**
518      * Adds or updates a text view for the data element.
519      */
setData(char[] text, int size)520     public void setData(char[] text, int size) {
521         if (text == null || size == 0) {
522             if (mDataView != null) {
523                 mDataView.setVisibility(View.GONE);
524             }
525             return;
526         } else {
527             getDataView();
528             mDataView.setText(text, 0, size);
529             mDataView.setVisibility(VISIBLE);
530         }
531     }
532 
533     /**
534      * Returns the text view for the data text, creating it if necessary.
535      */
getDataView()536     public TextView getDataView() {
537         if (mDataView == null) {
538             mDataView = new TextView(mContext);
539             mDataView.setSingleLine(true);
540             mDataView.setEllipsize(TruncateAt.MARQUEE);
541             mDataView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
542             addView(mDataView);
543         }
544         return mDataView;
545     }
546 
547     /**
548      * Adds or updates a text view for the search snippet.
549      */
setSnippet(CharSequence text)550     public void setSnippet(CharSequence text) {
551         if (TextUtils.isEmpty(text)) {
552             if (mSnippetView != null) {
553                 mSnippetView.setVisibility(View.GONE);
554             }
555         } else {
556             getSnippetView();
557             mSnippetView.setText(text);
558             mSnippetView.setVisibility(VISIBLE);
559         }
560     }
561 
562     /**
563      * Returns the text view for the search snippet, creating it if necessary.
564      */
getSnippetView()565     public TextView getSnippetView() {
566         if (mSnippetView == null) {
567             mSnippetView = new TextView(mContext);
568             mSnippetView.setSingleLine(true);
569             mSnippetView.setEllipsize(TruncateAt.MARQUEE);
570             mSnippetView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
571             mSnippetView.setTypeface(mSnippetView.getTypeface(), Typeface.BOLD);
572             addView(mSnippetView);
573         }
574         return mSnippetView;
575     }
576 
577     /**
578      * Adds or updates the presence icon view.
579      */
setPresence(Drawable icon)580     public void setPresence(Drawable icon) {
581         if (icon != null) {
582             if (mPresenceIcon == null) {
583                 mPresenceIcon = new ImageView(mContext);
584                 addView(mPresenceIcon);
585             }
586             mPresenceIcon.setImageDrawable(icon);
587             mPresenceIcon.setScaleType(ScaleType.CENTER);
588             mPresenceIcon.setVisibility(View.VISIBLE);
589         } else {
590             if (mPresenceIcon != null) {
591                 mPresenceIcon.setVisibility(View.GONE);
592             }
593         }
594     }
595 }
596