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 53 public ConversationMessageView(Context context) { 54 this(context, null); 55 } 56 57 public ConversationMessageView(final Context context, final AttributeSet attrs) { 58 this(context, attrs, 0); 59 } 60 61 public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr) { 62 this(context, attrs, defStyleAttr, 0); 63 } 64 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 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 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 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 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 166 private static boolean isLayoutRtl(final View view) { 167 return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection(); 168 } 169 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 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 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 253 private static Drawable getTintedDrawable(final Context context, final Drawable drawable, 254 final int color) { 255 // For some reason occassionally 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