Home | History | Annotate | Download | only in display
      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 
     52     public ConversationMessageView(Context context) {
     53         this(context, null);
     54     }
     55 
     56     public ConversationMessageView(final Context context, final AttributeSet attrs) {
     57         this(context, attrs, 0);
     58     }
     59 
     60     public ConversationMessageView(Context context, AttributeSet attrs, int defStyleAttr) {
     61         this(context, attrs, defStyleAttr, 0);
     62     }
     63 
     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
     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
     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
    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 
    165     private static boolean isLayoutRtl(final View view) {
    166         return View.LAYOUT_DIRECTION_RTL == view.getLayoutDirection();
    167     }
    168 
    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 
    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 
    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 
    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