| /* |
| * 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.graphics.drawable.LayerDrawable; |
| import android.text.TextUtils; |
| 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 messageBubblePadding = |
| getResources().getDimensionPixelOffset(R.dimen.message_bubble_left_right_padding); |
| |
| // 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 |
| - messageBubblePadding - 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); |
| mStatusTextView.setVisibility(TextUtils.isEmpty(mTimestampText) ? GONE : VISIBLE); |
| mContactIconView.setText(mIconText); |
| |
| mContactIconView.setTextColor(mIconTextColor); |
| final Drawable iconBackground = getContext().getDrawable( |
| R.drawable.conversation_message_icon); |
| final Drawable icon = getContext().getDrawable(R.drawable.ic_person); |
| final LayerDrawable layerDrawable = new LayerDrawable( |
| new Drawable[]{ |
| getTintedDrawable(getContext(), iconBackground, mIconBackgroundColor), |
| getTintedDrawable(getContext(), icon, |
| getContext().getColor(R.color.message_icon_color))}); |
| final int iconInset = getResources().getDimensionPixelOffset(R.dimen.message_icon_inset); |
| layerDrawable.setLayerInset(/* index= */ 1, iconInset, iconInset, iconInset, iconInset); |
| mContactIconView.setBackground(layerDrawable); |
| } |
| |
| private void updateViewAppearance() { |
| final Resources res = getResources(); |
| |
| final int messageBubbleLeftRightPadding = res.getDimensionPixelOffset( |
| R.dimen.message_bubble_left_right_padding); |
| 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; |
| textLeftPadding = messageTextLeftRightPadding; |
| textRightPadding = messageTextLeftRightPadding; |
| |
| final int messageBubbleLeftPadding = mIncoming ? messageBubbleLeftRightPadding : 0; |
| final int messageBubbleRightPadding = mIncoming ? 0 : messageBubbleLeftRightPadding; |
| |
| // 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(R.drawable.conversation_message_text_info_view_background), |
| 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); |
| mMessageBubble.setPaddingRelative(messageBubbleLeftPadding, /* top= */ 0, |
| messageBubbleRightPadding, /* bottom= */ 0); |
| |
| 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; |
| } |
| } |