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 
     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