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;
42
43/**
44 * This class manages the view for given conversation.
45 */
46public 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