• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.settings.display;
18 
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.content.res.TypedArray;
22 import android.graphics.PorterDuff;
23 import android.graphics.drawable.Drawable;
24 import android.util.AttributeSet;
25 import android.view.Gravity;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.FrameLayout;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 import com.android.settings.R;
33 
34 /**
35  * The view for a single entry in a conversation. This is a simplified version of
36  * com.android.messaging.ui.conversation.ConversationMessageView class.
37  */
38 public class ConversationMessageView extends FrameLayout {
39     private final boolean mIncoming;
40     private final CharSequence mMessageText;
41     private final CharSequence mTimestampText;
42     private final CharSequence mIconText;
43     private final int mIconTextColor;
44     private final int mIconBackgroundColor;
45 
46     private LinearLayout mMessageBubble;
47     private ViewGroup mMessageTextAndInfoView;
48     private TextView mMessageTextView;
49     private TextView mStatusTextView;
50     private TextView mContactIconView;
51 
ConversationMessageView(Context context)52     public ConversationMessageView(Context context) {
53         this(context, null);
54     }
55 
ConversationMessageView(final Context context, final AttributeSet attrs)56     public ConversationMessageView(final Context context, final AttributeSet attrs) {
57         this(context, attrs, 0);
58     }
59 
ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr)60     public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr) {
61         this(context, attrs, defStyleAttr, 0);
62     }
63 
ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)64     public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr,
65             int defStyleRes) {
66         super(context, attrs, defStyleAttr, defStyleRes);
67 
68         final TypedArray a = context.obtainStyledAttributes(attrs,
69                 R.styleable.ConversationMessageView);
70 
71         mIncoming = a.getBoolean(R.styleable.ConversationMessageView_incoming, true);
72         mMessageText = a.getString(R.styleable.ConversationMessageView_messageText);
73         mTimestampText = a.getString(R.styleable.ConversationMessageView_timestampText);
74         mIconText = a.getString(R.styleable.ConversationMessageView_iconText);
75         mIconTextColor = a.getColor(R.styleable.ConversationMessageView_iconTextColor, 0);
76         mIconBackgroundColor = a.getColor(R.styleable.ConversationMessageView_iconBackgroundColor,
77                 0);
78         a.recycle();
79         LayoutInflater.from(context).inflate(R.layout.conversation_message_icon, this);
80         LayoutInflater.from(context).inflate(R.layout.conversation_message_content, this);
81     }
82 
83     @Override
onFinishInflate()84     protected void onFinishInflate() {
85         mMessageBubble = (LinearLayout) findViewById(R.id.message_content);
86         mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info);
87         mMessageTextView = (TextView) findViewById(R.id.message_text);
88         mStatusTextView = (TextView) findViewById(R.id.message_status);
89         mContactIconView = (TextView) findViewById(R.id.conversation_icon);
90         updateViewContent();
91     }
92 
93     @Override
onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)94     protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
95         updateViewAppearance();
96 
97         final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec);
98 
99         final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
100         int iconMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
101 
102         mContactIconView.measure(iconMeasureSpec, iconMeasureSpec);
103         iconMeasureSpec = MeasureSpec.makeMeasureSpec(
104                 Math.max(mContactIconView.getMeasuredWidth(), mContactIconView.getMeasuredHeight()),
105                 MeasureSpec.EXACTLY);
106         mContactIconView.measure(iconMeasureSpec, iconMeasureSpec);
107 
108         final int arrowWidth =
109                 getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width);
110 
111         // We need to subtract contact icon width twice from the horizontal space to get
112         // the max leftover space because we want the message bubble to extend no further than the
113         // starting position of the message bubble in the opposite direction.
114         final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2
115                 - arrowWidth - getPaddingLeft() - getPaddingRight();
116         final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace,
117                 MeasureSpec.AT_MOST);
118 
119         mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec);
120 
121         final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(),
122                 mMessageBubble.getMeasuredHeight());
123         setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop());
124     }
125 
126     @Override
onLayout(final boolean changed, final int left, final int top, final int right, final int bottom)127     protected void onLayout(final boolean changed, final int left, final int top, final int right,
128             final int bottom) {
129         final boolean isRtl = isLayoutRtl(this);
130 
131         final int iconWidth = mContactIconView.getMeasuredWidth();
132         final int iconHeight = mContactIconView.getMeasuredHeight();
133         final int iconTop = getPaddingTop();
134         final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight();
135         final int contentHeight = mMessageBubble.getMeasuredHeight();
136         final int contentTop = iconTop;
137 
138         final int iconLeft;
139         final int contentLeft;
140 
141         if (mIncoming) {
142             if (isRtl) {
143                 iconLeft = (right - left) - getPaddingRight() - iconWidth;
144                 contentLeft = iconLeft - contentWidth;
145             } else {
146                 iconLeft = getPaddingLeft();
147                 contentLeft = iconLeft + iconWidth;
148             }
149         } else {
150             if (isRtl) {
151                 iconLeft = getPaddingLeft();
152                 contentLeft = iconLeft + iconWidth;
153             } else {
154                 iconLeft = (right - left) - getPaddingRight() - iconWidth;
155                 contentLeft = iconLeft - contentWidth;
156             }
157         }
158 
159         mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight);
160 
161         mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth,
162                 contentTop + contentHeight);
163     }
164 
isLayoutRtl(final View view)165     private static boolean isLayoutRtl(final View view) {
166         return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
167     }
168 
updateViewContent()169     private void updateViewContent() {
170         mMessageTextView.setText(mMessageText);
171         mStatusTextView.setText(mTimestampText);
172         mContactIconView.setText(mIconText);
173 
174         mContactIconView.setTextColor(mIconTextColor);
175         final Drawable iconBase = getContext().getDrawable(R.drawable.conversation_message_icon);
176         mContactIconView
177                 .setBackground(getTintedDrawable(getContext(), iconBase, mIconBackgroundColor));
178     }
179 
updateViewAppearance()180     private void updateViewAppearance() {
181         final Resources res = getResources();
182 
183         final int arrowWidth = res.getDimensionPixelOffset(
184                 R.dimen.message_bubble_arrow_width);
185         final int messageTextLeftRightPadding = res.getDimensionPixelOffset(
186                 R.dimen.message_text_left_right_padding);
187         final int textTopPadding = res.getDimensionPixelOffset(
188                 R.dimen.message_text_top_padding);
189         final int textBottomPadding = res.getDimensionPixelOffset(
190                 R.dimen.message_text_bottom_padding);
191 
192         final int textLeftPadding, textRightPadding;
193 
194         if (mIncoming) {
195             textLeftPadding = messageTextLeftRightPadding + arrowWidth;
196             textRightPadding = messageTextLeftRightPadding;
197         } else {
198             textLeftPadding = messageTextLeftRightPadding;
199             textRightPadding = messageTextLeftRightPadding + arrowWidth;
200         }
201 
202         // These values do not depend on whether the message includes attachments
203         final int gravity = mIncoming ? (Gravity.START | Gravity.CENTER_VERTICAL) :
204                 (Gravity.END | Gravity.CENTER_VERTICAL);
205         final int messageTopPadding = res.getDimensionPixelSize(
206                 R.dimen.message_padding_default);
207         final int metadataTopPadding =  res.getDimensionPixelOffset(
208                 R.dimen.message_metadata_top_padding);
209 
210         // Update the message text/info views
211         final int bubbleDrawableResId = mIncoming ? R.drawable.msg_bubble_incoming
212                 : R.drawable.msg_bubble_outgoing;
213         final int bubbleColorResId = mIncoming ? R.color.message_bubble_incoming
214                 : R.color.message_bubble_outgoing;
215         final Context context = getContext();
216 
217         final Drawable textBackgroundDrawable = getTintedDrawable(context,
218                 context.getDrawable(bubbleDrawableResId),
219                 context.getColor(bubbleColorResId));
220         mMessageTextAndInfoView.setBackground(textBackgroundDrawable);
221 
222         if (isLayoutRtl(this)) {
223             // Need to switch right and left padding in RtL mode
224             mMessageTextAndInfoView.setPadding(textRightPadding,
225                     textTopPadding + metadataTopPadding,
226                     textLeftPadding, textBottomPadding);
227         } else {
228             mMessageTextAndInfoView.setPadding(textLeftPadding,
229                     textTopPadding + metadataTopPadding,
230                     textRightPadding, textBottomPadding);
231         }
232 
233         // Update the message row and message bubble views
234         setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0);
235         mMessageBubble.setGravity(gravity);
236 
237         updateTextAppearance();
238     }
239 
updateTextAppearance()240     private void updateTextAppearance() {
241         final int messageColorResId = (mIncoming ? R.color.message_text_incoming
242                 : R.color.message_text_outgoing);
243         final int timestampColorResId = mIncoming ? R.color.timestamp_text_incoming
244                 : R.color.timestamp_text_outgoing;
245         final int messageColor = getContext().getColor(messageColorResId);
246 
247         mMessageTextView.setTextColor(messageColor);
248         mMessageTextView.setLinkTextColor(messageColor);
249         mStatusTextView.setTextColor(timestampColorResId);
250     }
251 
getTintedDrawable(final Context context, final Drawable drawable, final int color)252     private static Drawable getTintedDrawable(final Context context, final Drawable drawable,
253             final int color) {
254         // For some reason occasionally drawables on JB has a null constant state
255         final Drawable.ConstantState constantStateDrawable = drawable.getConstantState();
256         final Drawable retDrawable = (constantStateDrawable != null)
257                 ? constantStateDrawable.newDrawable(context.getResources()).mutate()
258                 : drawable;
259         retDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
260         return retDrawable;
261     }
262 }
263