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