/* * 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); a.recycle(); 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 unspecifiedMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); int iconMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); mContactIconView.measure(iconMeasureSpec, iconMeasureSpec); iconMeasureSpec = MeasureSpec.makeMeasureSpec( Math.max(mContactIconView.getMeasuredWidth(), mContactIconView.getMeasuredHeight()), 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 occasionally 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; } }