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