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 import com.android.mms.util.SmileyParser; 43 44 /** 45 * This class manages the view for given conversation. 46 */ 47 public 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