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