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