Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2008 Esmertec AG.
      3  * Copyright (C) 2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mms.ui;
     19 
     20 import android.content.Context;
     21 import android.graphics.Typeface;
     22 import android.graphics.drawable.Drawable;
     23 import android.os.Handler;
     24 import android.text.Spannable;
     25 import android.text.SpannableStringBuilder;
     26 import android.text.style.ForegroundColorSpan;
     27 import android.text.style.StyleSpan;
     28 import android.text.style.TextAppearanceSpan;
     29 import android.util.AttributeSet;
     30 import android.util.Log;
     31 import android.view.View;
     32 import android.widget.Checkable;
     33 import android.widget.QuickContactBadge;
     34 import android.widget.RelativeLayout;
     35 import android.widget.TextView;
     36 
     37 import com.android.mms.LogTag;
     38 import com.android.mms.R;
     39 import com.android.mms.data.Contact;
     40 import com.android.mms.data.ContactList;
     41 import com.android.mms.data.Conversation;
     42 
     43 /**
     44  * This class manages the view for given conversation.
     45  */
     46 public class ConversationListItem extends RelativeLayout implements Contact.UpdateListener,
     47             Checkable {
     48     private static final String TAG = LogTag.TAG;
     49     private static final boolean DEBUG = false;
     50 
     51     private TextView mSubjectView;
     52     private TextView mFromView;
     53     private TextView mDateView;
     54     private View mAttachmentView;
     55     private View mErrorIndicator;
     56     private QuickContactBadge mAvatarView;
     57 
     58     static private Drawable sDefaultContactImage;
     59 
     60     // For posting UI update Runnables from other threads:
     61     private Handler mHandler = new Handler();
     62 
     63     private Conversation mConversation;
     64 
     65     public static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
     66 
     67     public ConversationListItem(Context context) {
     68         super(context);
     69     }
     70 
     71     public ConversationListItem(Context context, AttributeSet attrs) {
     72         super(context, attrs);
     73 
     74         if (sDefaultContactImage == null) {
     75             sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture);
     76         }
     77     }
     78 
     79     @Override
     80     protected void onFinishInflate() {
     81         super.onFinishInflate();
     82 
     83         mFromView = (TextView) findViewById(R.id.from);
     84         mSubjectView = (TextView) findViewById(R.id.subject);
     85 
     86         mDateView = (TextView) findViewById(R.id.date);
     87         mAttachmentView = findViewById(R.id.attachment);
     88         mErrorIndicator = findViewById(R.id.error);
     89         mAvatarView = (QuickContactBadge) findViewById(R.id.avatar);
     90     }
     91 
     92     public Conversation getConversation() {
     93         return mConversation;
     94     }
     95 
     96     /**
     97      * Only used for header binding.
     98      */
     99     public void bind(String title, String explain) {
    100         mFromView.setText(title);
    101         mSubjectView.setText(explain);
    102     }
    103 
    104     private CharSequence formatMessage() {
    105         final int color = android.R.styleable.Theme_textColorSecondary;
    106         String from = mConversation.getRecipients().formatNames(", ");
    107 
    108         SpannableStringBuilder buf = new SpannableStringBuilder(from);
    109 
    110         if (mConversation.getMessageCount() > 1) {
    111             int before = buf.length();
    112             buf.append(mContext.getResources().getString(R.string.message_count_format,
    113                     mConversation.getMessageCount()));
    114             buf.setSpan(new ForegroundColorSpan(
    115                     mContext.getResources().getColor(R.color.message_count_color)),
    116                     before, buf.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    117         }
    118         if (mConversation.hasDraft()) {
    119             buf.append(mContext.getResources().getString(R.string.draft_separator));
    120             int before = buf.length();
    121             int size;
    122             buf.append(mContext.getResources().getString(R.string.has_draft));
    123             size = android.R.style.TextAppearance_Small;
    124             buf.setSpan(new TextAppearanceSpan(mContext, size, color), before,
    125                     buf.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    126             buf.setSpan(new ForegroundColorSpan(
    127                     mContext.getResources().getColor(R.drawable.text_color_red)),
    128                     before, buf.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    129         }
    130 
    131         // Unread messages are shown in bold
    132         if (mConversation.hasUnreadMessages()) {
    133             buf.setSpan(STYLE_BOLD, 0, buf.length(),
    134                     Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
    135         }
    136         return buf;
    137     }
    138 
    139     private void updateAvatarView() {
    140         Drawable avatarDrawable;
    141         if (mConversation.getRecipients().size() == 1) {
    142             Contact contact = mConversation.getRecipients().get(0);
    143             avatarDrawable = contact.getAvatar(mContext, sDefaultContactImage);
    144 
    145             if (contact.existsInDatabase()) {
    146                 mAvatarView.assignContactUri(contact.getUri());
    147             } else {
    148                 mAvatarView.assignContactFromPhone(contact.getNumber(), true);
    149             }
    150         } else {
    151             // TODO get a multiple recipients asset (or do something else)
    152             avatarDrawable = sDefaultContactImage;
    153             mAvatarView.assignContactUri(null);
    154         }
    155         mAvatarView.setImageDrawable(avatarDrawable);
    156         mAvatarView.setVisibility(View.VISIBLE);
    157     }
    158 
    159     private void updateFromView() {
    160         mFromView.setText(formatMessage());
    161         updateAvatarView();
    162     }
    163 
    164     public void onUpdate(Contact updated) {
    165         if (Log.isLoggable(LogTag.CONTACT, Log.DEBUG)) {
    166             Log.v(TAG, "onUpdate: " + this + " contact: " + updated);
    167         }
    168         mHandler.post(new Runnable() {
    169             public void run() {
    170                 updateFromView();
    171             }
    172         });
    173     }
    174 
    175     public final void bind(Context context, final Conversation conversation) {
    176         //if (DEBUG) Log.v(TAG, "bind()");
    177 
    178         mConversation = conversation;
    179 
    180         updateBackground();
    181 
    182         LayoutParams attachmentLayout = (LayoutParams)mAttachmentView.getLayoutParams();
    183         boolean hasError = conversation.hasError();
    184         // When there's an error icon, the attachment icon is left of the error icon.
    185         // When there is not an error icon, the attachment icon is left of the date text.
    186         // As far as I know, there's no way to specify that relationship in xml.
    187         if (hasError) {
    188             attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.error);
    189         } else {
    190             attachmentLayout.addRule(RelativeLayout.LEFT_OF, R.id.date);
    191         }
    192 
    193         boolean hasAttachment = conversation.hasAttachment();
    194         mAttachmentView.setVisibility(hasAttachment ? VISIBLE : GONE);
    195 
    196         // Date
    197         mDateView.setText(MessageUtils.formatTimeStampString(context, conversation.getDate()));
    198 
    199         // From.
    200         mFromView.setText(formatMessage());
    201 
    202         // Register for updates in changes of any of the contacts in this conversation.
    203         ContactList contacts = conversation.getRecipients();
    204 
    205         if (Log.isLoggable(LogTag.CONTACT, Log.DEBUG)) {
    206             Log.v(TAG, "bind: contacts.addListeners " + this);
    207         }
    208         Contact.addListener(this);
    209 
    210         // Subject
    211         mSubjectView.setText(conversation.getSnippet());
    212         LayoutParams subjectLayout = (LayoutParams)mSubjectView.getLayoutParams();
    213         // We have to make the subject left of whatever optional items are shown on the right.
    214         subjectLayout.addRule(RelativeLayout.LEFT_OF, hasAttachment ? R.id.attachment :
    215             (hasError ? R.id.error : R.id.date));
    216 
    217         // Transmission error indicator.
    218         mErrorIndicator.setVisibility(hasError ? VISIBLE : GONE);
    219 
    220         updateAvatarView();
    221     }
    222 
    223     private void updateBackground() {
    224         int backgroundId;
    225         if (mConversation.isChecked()) {
    226             backgroundId = R.drawable.list_selected_holo_light;
    227         } else if (mConversation.hasUnreadMessages()) {
    228             backgroundId = R.drawable.conversation_item_background_unread;
    229         } else {
    230             backgroundId = R.drawable.conversation_item_background_read;
    231         }
    232         Drawable background = mContext.getResources().getDrawable(backgroundId);
    233         setBackground(background);
    234     }
    235 
    236     public final void unbind() {
    237         if (Log.isLoggable(LogTag.CONTACT, Log.DEBUG)) {
    238             Log.v(TAG, "unbind: contacts.removeListeners " + this);
    239         }
    240         // Unregister contact update callbacks.
    241         Contact.removeListener(this);
    242     }
    243 
    244     public void setChecked(boolean checked) {
    245         mConversation.setIsChecked(checked);
    246         updateBackground();
    247     }
    248 
    249     public boolean isChecked() {
    250         return mConversation.isChecked();
    251     }
    252 
    253     public void toggle() {
    254         mConversation.setIsChecked(!mConversation.isChecked());
    255     }
    256 }
    257