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