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 43 /** 44 * This class manages the view for given conversation. 45 */ 46 public class ConversationListItem extends RelativeLayout implements Contact.UpdateListener, 47 Checkable { 48 private static final String TAG = "ConversationListItem"; 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 ConversationListItem(Context context)67 public ConversationListItem(Context context) { 68 super(context); 69 } 70 ConversationListItem(Context context, AttributeSet attrs)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 onFinishInflate()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 getConversation()92 public Conversation getConversation() { 93 return mConversation; 94 } 95 96 /** 97 * Only used for header binding. 98 */ bind(String title, String explain)99 public void bind(String title, String explain) { 100 mFromView.setText(title); 101 mSubjectView.setText(explain); 102 } 103 formatMessage()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 updateAvatarView()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 updateFromView()159 private void updateFromView() { 160 mFromView.setText(formatMessage()); 161 updateAvatarView(); 162 } 163 onUpdate(Contact updated)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 bind(Context context, final Conversation conversation)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 updateBackground()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 unbind()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 setChecked(boolean checked)244 public void setChecked(boolean checked) { 245 mConversation.setIsChecked(checked); 246 updateBackground(); 247 } 248 isChecked()249 public boolean isChecked() { 250 return mConversation.isChecked(); 251 } 252 toggle()253 public void toggle() { 254 mConversation.setIsChecked(!mConversation.isChecked()); 255 } 256 } 257