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