| /* | |
| * Copyright (C) 2016 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package com.android.settings.display; | |
| import android.content.Context; | |
| import android.content.res.Resources; | |
| import android.content.res.TypedArray; | |
| import android.graphics.PorterDuff; | |
| import android.graphics.drawable.Drawable; | |
| import android.util.AttributeSet; | |
| import android.view.Gravity; | |
| import android.view.LayoutInflater; | |
| import android.view.View; | |
| import android.view.ViewGroup; | |
| import android.widget.FrameLayout; | |
| import android.widget.LinearLayout; | |
| import android.widget.TextView; | |
| import com.android.settings.R; | |
| /** | |
| * The view for a single entry in a conversation. This is a simplified version of | |
| * com.android.messaging.ui.conversation.ConversationMessageView class. | |
| */ | |
| public class ConversationMessageView extends FrameLayout { | |
| private final boolean mIncoming; | |
| private final CharSequence mMessageText; | |
| private final CharSequence mTimestampText; | |
| private final CharSequence mIconText; | |
| private final int mIconTextColor; | |
| private final int mIconBackgroundColor; | |
| private LinearLayout mMessageBubble; | |
| private ViewGroup mMessageTextAndInfoView; | |
| private TextView mMessageTextView; | |
| private TextView mStatusTextView; | |
| private TextView mContactIconView; | |
| public ConversationMessageView(Context context) { | |
| this(context, null); | |
| } | |
| public ConversationMessageView(final Context context, final AttributeSet attrs) { | |
| this(context, attrs, 0); | |
| } | |
| public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr) { | |
| this(context, attrs, defStyleAttr, 0); | |
| } | |
| public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr, | |
| int defStyleRes) { | |
| super(context, attrs, defStyleAttr, defStyleRes); | |
| final TypedArray a = context.obtainStyledAttributes(attrs, | |
| R.styleable.ConversationMessageView); | |
| mIncoming = a.getBoolean(R.styleable.ConversationMessageView_incoming, true); | |
| mMessageText = a.getString(R.styleable.ConversationMessageView_messageText); | |
| mTimestampText = a.getString(R.styleable.ConversationMessageView_timestampText); | |
| mIconText = a.getString(R.styleable.ConversationMessageView_iconText); | |
| mIconTextColor = a.getColor(R.styleable.ConversationMessageView_iconTextColor, 0); | |
| mIconBackgroundColor = a.getColor(R.styleable.ConversationMessageView_iconBackgroundColor, | |
| 0); | |
| LayoutInflater.from(context).inflate(R.layout.conversation_message_icon, this); | |
| LayoutInflater.from(context).inflate(R.layout.conversation_message_content, this); | |
| } | |
| @Override | |
| protected void onFinishInflate() { | |
| mMessageBubble = (LinearLayout) findViewById(R.id.message_content); | |
| mMessageTextAndInfoView = (ViewGroup) findViewById(R.id.message_text_and_info); | |
| mMessageTextView = (TextView) findViewById(R.id.message_text); | |
| mStatusTextView = (TextView) findViewById(R.id.message_status); | |
| mContactIconView = (TextView) findViewById(R.id.conversation_icon); | |
| updateViewContent(); | |
| } | |
| @Override | |
| protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { | |
| updateViewAppearance(); | |
| final int horizontalSpace = MeasureSpec.getSize(widthMeasureSpec); | |
| final int iconSize = getResources() | |
| .getDimensionPixelSize(R.dimen.conversation_message_contact_icon_size); | |
| final int unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); | |
| final int iconMeasureSpec = MeasureSpec.makeMeasureSpec(iconSize, MeasureSpec.EXACTLY); | |
| mContactIconView.measure(iconMeasureSpec, iconMeasureSpec); | |
| final int arrowWidth = | |
| getResources().getDimensionPixelSize(R.dimen.message_bubble_arrow_width); | |
| // We need to subtract contact icon width twice from the horizontal space to get | |
| // the max leftover space because we want the message bubble to extend no further than the | |
| // starting position of the message bubble in the opposite direction. | |
| final int maxLeftoverSpace = horizontalSpace - mContactIconView.getMeasuredWidth() * 2 | |
| - arrowWidth - getPaddingLeft() - getPaddingRight(); | |
| final int messageContentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(maxLeftoverSpace, | |
| MeasureSpec.AT_MOST); | |
| mMessageBubble.measure(messageContentWidthMeasureSpec, unspecifiedMeasureSpec); | |
| final int maxHeight = Math.max(mContactIconView.getMeasuredHeight(), | |
| mMessageBubble.getMeasuredHeight()); | |
| setMeasuredDimension(horizontalSpace, maxHeight + getPaddingBottom() + getPaddingTop()); | |
| } | |
| @Override | |
| protected void onLayout(final boolean changed, final int left, final int top, final int right, | |
| final int bottom) { | |
| final boolean isRtl = isLayoutRtl(this); | |
| final int iconWidth = mContactIconView.getMeasuredWidth(); | |
| final int iconHeight = mContactIconView.getMeasuredHeight(); | |
| final int iconTop = getPaddingTop(); | |
| final int contentWidth = (right -left) - iconWidth - getPaddingLeft() - getPaddingRight(); | |
| final int contentHeight = mMessageBubble.getMeasuredHeight(); | |
| final int contentTop = iconTop; | |
| final int iconLeft; | |
| final int contentLeft; | |
| if (mIncoming) { | |
| if (isRtl) { | |
| iconLeft = (right - left) - getPaddingRight() - iconWidth; | |
| contentLeft = iconLeft - contentWidth; | |
| } else { | |
| iconLeft = getPaddingLeft(); | |
| contentLeft = iconLeft + iconWidth; | |
| } | |
| } else { | |
| if (isRtl) { | |
| iconLeft = getPaddingLeft(); | |
| contentLeft = iconLeft + iconWidth; | |
| } else { | |
| iconLeft = (right - left) - getPaddingRight() - iconWidth; | |
| contentLeft = iconLeft - contentWidth; | |
| } | |
| } | |
| mContactIconView.layout(iconLeft, iconTop, iconLeft + iconWidth, iconTop + iconHeight); | |
| mMessageBubble.layout(contentLeft, contentTop, contentLeft + contentWidth, | |
| contentTop + contentHeight); | |
| } | |
| private static boolean isLayoutRtl(final View view) { | |
| return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection(); | |
| } | |
| private void updateViewContent() { | |
| mMessageTextView.setText(mMessageText); | |
| mStatusTextView.setText(mTimestampText); | |
| mContactIconView.setText(mIconText); | |
| mContactIconView.setTextColor(mIconTextColor); | |
| final Drawable iconBase = getContext().getDrawable(R.drawable.conversation_message_icon); | |
| mContactIconView | |
| .setBackground(getTintedDrawable(getContext(), iconBase, mIconBackgroundColor)); | |
| } | |
| private void updateViewAppearance() { | |
| final Resources res = getResources(); | |
| final int arrowWidth = res.getDimensionPixelOffset( | |
| R.dimen.message_bubble_arrow_width); | |
| final int messageTextLeftRightPadding = res.getDimensionPixelOffset( | |
| R.dimen.message_text_left_right_padding); | |
| final int textTopPadding = res.getDimensionPixelOffset( | |
| R.dimen.message_text_top_padding); | |
| final int textBottomPadding = res.getDimensionPixelOffset( | |
| R.dimen.message_text_bottom_padding); | |
| final int textLeftPadding, textRightPadding; | |
| if (mIncoming) { | |
| textLeftPadding = messageTextLeftRightPadding + arrowWidth; | |
| textRightPadding = messageTextLeftRightPadding; | |
| } else { | |
| textLeftPadding = messageTextLeftRightPadding; | |
| textRightPadding = messageTextLeftRightPadding + arrowWidth; | |
| } | |
| // These values do not depend on whether the message includes attachments | |
| final int gravity = mIncoming ? (Gravity.START | Gravity.CENTER_VERTICAL) : | |
| (Gravity.END | Gravity.CENTER_VERTICAL); | |
| final int messageTopPadding = res.getDimensionPixelSize( | |
| R.dimen.message_padding_default); | |
| final int metadataTopPadding = res.getDimensionPixelOffset( | |
| R.dimen.message_metadata_top_padding); | |
| // Update the message text/info views | |
| final int bubbleDrawableResId = mIncoming ? R.drawable.msg_bubble_incoming | |
| : R.drawable.msg_bubble_outgoing; | |
| final int bubbleColorResId = mIncoming ? R.color.message_bubble_incoming | |
| : R.color.message_bubble_outgoing; | |
| final Context context = getContext(); | |
| final Drawable textBackgroundDrawable = getTintedDrawable(context, | |
| context.getDrawable(bubbleDrawableResId), | |
| context.getColor(bubbleColorResId)); | |
| mMessageTextAndInfoView.setBackground(textBackgroundDrawable); | |
| if (isLayoutRtl(this)) { | |
| // Need to switch right and left padding in RtL mode | |
| mMessageTextAndInfoView.setPadding(textRightPadding, | |
| textTopPadding + metadataTopPadding, | |
| textLeftPadding, textBottomPadding); | |
| } else { | |
| mMessageTextAndInfoView.setPadding(textLeftPadding, | |
| textTopPadding + metadataTopPadding, | |
| textRightPadding, textBottomPadding); | |
| } | |
| // Update the message row and message bubble views | |
| setPadding(getPaddingLeft(), messageTopPadding, getPaddingRight(), 0); | |
| mMessageBubble.setGravity(gravity); | |
| updateTextAppearance(); | |
| } | |
| private void updateTextAppearance() { | |
| final int messageColorResId = (mIncoming ? R.color.message_text_incoming | |
| : R.color.message_text_outgoing); | |
| final int timestampColorResId = mIncoming ? R.color.timestamp_text_incoming | |
| : R.color.timestamp_text_outgoing; | |
| final int messageColor = getContext().getColor(messageColorResId); | |
| mMessageTextView.setTextColor(messageColor); | |
| mMessageTextView.setLinkTextColor(messageColor); | |
| mStatusTextView.setTextColor(timestampColorResId); | |
| } | |
| private static Drawable getTintedDrawable(final Context context, final Drawable drawable, | |
| final int color) { | |
| // For some reason occassionally drawables on JB has a null constant state | |
| final Drawable.ConstantState constantStateDrawable = drawable.getConstantState(); | |
| final Drawable retDrawable = (constantStateDrawable != null) | |
| ? constantStateDrawable.newDrawable(context.getResources()).mutate() | |
| : drawable; | |
| retDrawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP); | |
| return retDrawable; | |
| } | |
| } |