MessageListItem.java revision 79bf6f70c54d08dc9c3481b8461a3a46a3cefb83
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 java.util.Map; 21import java.util.regex.Matcher; 22import java.util.regex.Pattern; 23 24import android.app.AlertDialog; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.graphics.Bitmap; 29import android.graphics.BitmapFactory; 30import android.graphics.Canvas; 31import android.graphics.Paint; 32import android.graphics.Paint.FontMetricsInt; 33import android.graphics.Path; 34import android.graphics.Typeface; 35import android.graphics.drawable.Drawable; 36import android.net.Uri; 37import android.os.Handler; 38import android.os.Message; 39import android.provider.ContactsContract.Profile; 40import android.provider.Telephony.Sms; 41import android.telephony.PhoneNumberUtils; 42import android.telephony.TelephonyManager; 43import android.text.Html; 44import android.text.SpannableStringBuilder; 45import android.text.TextUtils; 46import android.text.method.HideReturnsTransformationMethod; 47import android.text.style.ForegroundColorSpan; 48import android.text.style.LineHeightSpan; 49import android.text.style.StyleSpan; 50import android.text.style.TextAppearanceSpan; 51import android.text.style.URLSpan; 52import android.util.AttributeSet; 53import android.util.Log; 54import android.view.View; 55import android.view.View.OnClickListener; 56import android.view.ViewGroup; 57import android.widget.ArrayAdapter; 58import android.widget.Button; 59import android.widget.ImageButton; 60import android.widget.ImageView; 61import android.widget.LinearLayout; 62import android.widget.TextView; 63 64import com.android.mms.MmsApp; 65import com.android.mms.R; 66import com.android.mms.data.Contact; 67import com.android.mms.data.WorkingMessage; 68import com.android.mms.model.SlideModel; 69import com.android.mms.model.SlideshowModel; 70import com.android.mms.transaction.Transaction; 71import com.android.mms.transaction.TransactionBundle; 72import com.android.mms.transaction.TransactionService; 73import com.android.mms.util.DownloadManager; 74import com.android.mms.util.ItemLoadedCallback; 75import com.android.mms.util.SmileyParser; 76import com.android.mms.util.ThumbnailManager.ImageLoaded; 77import com.google.android.mms.ContentType; 78import com.google.android.mms.pdu.PduHeaders; 79 80/** 81 * This class provides view of a message in the messages list. 82 */ 83public class MessageListItem extends LinearLayout implements 84 SlideViewInterface, OnClickListener { 85 public static final String EXTRA_URLS = "com.android.mms.ExtraUrls"; 86 87 private static final String TAG = "MessageListItem"; 88 private static final boolean DEBUG = false; 89 90 static final int MSG_LIST_EDIT_MMS = 1; 91 static final int MSG_LIST_EDIT_SMS = 2; 92 93 private View mMmsView; 94 private ImageView mImageView; 95 private ImageView mLockedIndicator; 96 private ImageView mDeliveredIndicator; 97 private ImageView mDetailsIndicator; 98 private ImageButton mSlideShowButton; 99 private TextView mBodyTextView; 100 private Button mDownloadButton; 101 private TextView mDownloadingLabel; 102 private Handler mHandler; 103 private MessageItem mMessageItem; 104 private String mDefaultCountryIso; 105 private TextView mDateView; 106 public View mMessageBlock; 107 private Path mPath = new Path(); 108 private Paint mPaint = new Paint(); 109 private QuickContactDivot mAvatar; 110 private boolean mIsLastItemInList; 111 static private Drawable sDefaultContactImage; 112 private Presenter mPresenter; 113 114 public MessageListItem(Context context) { 115 super(context); 116 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 117 118 if (sDefaultContactImage == null) { 119 sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture); 120 } 121 } 122 123 public MessageListItem(Context context, AttributeSet attrs) { 124 super(context, attrs); 125 126 int color = mContext.getResources().getColor(R.color.timestamp_color); 127 mColorSpan = new ForegroundColorSpan(color); 128 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 129 130 if (sDefaultContactImage == null) { 131 sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture); 132 } 133 } 134 135 @Override 136 protected void onFinishInflate() { 137 super.onFinishInflate(); 138 139 mBodyTextView = (TextView) findViewById(R.id.text_view); 140 mDateView = (TextView) findViewById(R.id.date_view); 141 mLockedIndicator = (ImageView) findViewById(R.id.locked_indicator); 142 mDeliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator); 143 mDetailsIndicator = (ImageView) findViewById(R.id.details_indicator); 144 mAvatar = (QuickContactDivot) findViewById(R.id.avatar); 145 mMessageBlock = findViewById(R.id.message_block); 146 } 147 148 public void bind(MessageItem msgItem, boolean isLastItem) { 149 mMessageItem = msgItem; 150 mIsLastItemInList = isLastItem; 151 152 setLongClickable(false); 153 setClickable(false); // let the list view handle clicks on the item normally. When 154 // clickable is true, clicks bypass the listview and go straight 155 // to this listitem. We always want the listview to handle the 156 // clicks first. 157 158 switch (msgItem.mMessageType) { 159 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 160 bindNotifInd(); 161 break; 162 default: 163 bindCommonMessage(); 164 break; 165 } 166 } 167 168 public void unbind() { 169 // Clear all references to the message item, which can contain attachments and other 170 // memory-intensive objects 171 mMessageItem = null; 172 if (mImageView != null) { 173 // Because #setOnClickListener may have set the listener to an object that has the 174 // message item in its closure. 175 mImageView.setOnClickListener(null); 176 } 177 if (mSlideShowButton != null) { 178 // Because #drawPlaybackButton sets the tag to mMessageItem 179 mSlideShowButton.setTag(null); 180 } 181 // leave the presenter in case it's needed when rebound to a different MessageItem. 182 if (mPresenter != null) { 183 mPresenter.cancelBackgroundLoading(); 184 } 185 } 186 187 public MessageItem getMessageItem() { 188 return mMessageItem; 189 } 190 191 public void setMsgListItemHandler(Handler handler) { 192 mHandler = handler; 193 } 194 195 private void bindNotifInd() { 196 hideMmsViewIfNeeded(); 197 198 String msgSizeText = mContext.getString(R.string.message_size_label) 199 + String.valueOf((mMessageItem.mMessageSize + 1023) / 1024) 200 + mContext.getString(R.string.kilobyte); 201 202 mBodyTextView.setText(formatMessage(mMessageItem, mMessageItem.mContact, null, 203 mMessageItem.mSubject, 204 mMessageItem.mHighlight, 205 mMessageItem.mTextContentType)); 206 207 mDateView.setText(msgSizeText + " " + mMessageItem.mTimestamp); 208 209 switch (mMessageItem.getMmsDownloadStatus()) { 210 case DownloadManager.STATE_DOWNLOADING: 211 showDownloadingAttachment(); 212 break; 213 case DownloadManager.STATE_UNKNOWN: 214 case DownloadManager.STATE_UNSTARTED: 215 DownloadManager downloadManager = DownloadManager.getInstance(); 216 boolean autoDownload = downloadManager.isAuto(); 217 boolean dataSuspended = (MmsApp.getApplication().getTelephonyManager() 218 .getDataState() == TelephonyManager.DATA_SUSPENDED); 219 220 // If we're going to automatically start downloading the mms attachment, then 221 // don't bother showing the download button for an instant before the actual 222 // download begins. Instead, show downloading as taking place. 223 if (autoDownload && !dataSuspended) { 224 showDownloadingAttachment(); 225 break; 226 } 227 case DownloadManager.STATE_TRANSIENT_FAILURE: 228 case DownloadManager.STATE_PERMANENT_FAILURE: 229 default: 230 setLongClickable(true); 231 inflateDownloadControls(); 232 mDownloadingLabel.setVisibility(View.GONE); 233 mDownloadButton.setVisibility(View.VISIBLE); 234 mDownloadButton.setOnClickListener(new OnClickListener() { 235 @Override 236 public void onClick(View v) { 237 mDownloadingLabel.setVisibility(View.VISIBLE); 238 mDownloadButton.setVisibility(View.GONE); 239 Intent intent = new Intent(mContext, TransactionService.class); 240 intent.putExtra(TransactionBundle.URI, mMessageItem.mMessageUri.toString()); 241 intent.putExtra(TransactionBundle.TRANSACTION_TYPE, 242 Transaction.RETRIEVE_TRANSACTION); 243 mContext.startService(intent); 244 } 245 }); 246 break; 247 } 248 249 // Hide the indicators. 250 mLockedIndicator.setVisibility(View.GONE); 251 mDeliveredIndicator.setVisibility(View.GONE); 252 mDetailsIndicator.setVisibility(View.GONE); 253 updateAvatarView(mMessageItem.mAddress, false); 254 } 255 256 private void showDownloadingAttachment() { 257 inflateDownloadControls(); 258 mDownloadingLabel.setVisibility(View.VISIBLE); 259 mDownloadButton.setVisibility(View.GONE); 260 } 261 262 private void updateAvatarView(String addr, boolean isSelf) { 263 Drawable avatarDrawable; 264 if (isSelf || !TextUtils.isEmpty(addr)) { 265 Contact contact = isSelf ? Contact.getMe(false) : Contact.get(addr, false); 266 avatarDrawable = contact.getAvatar(mContext, sDefaultContactImage); 267 268 if (isSelf) { 269 mAvatar.assignContactUri(Profile.CONTENT_URI); 270 } else { 271 if (contact.existsInDatabase()) { 272 mAvatar.assignContactUri(contact.getUri()); 273 } else { 274 mAvatar.assignContactFromPhone(contact.getNumber(), true); 275 } 276 } 277 } else { 278 avatarDrawable = sDefaultContactImage; 279 } 280 mAvatar.setImageDrawable(avatarDrawable); 281 } 282 283 private void bindCommonMessage() { 284 if (mDownloadButton != null) { 285 mDownloadButton.setVisibility(View.GONE); 286 mDownloadingLabel.setVisibility(View.GONE); 287 } 288 // Since the message text should be concatenated with the sender's 289 // address(or name), I have to display it here instead of 290 // displaying it by the Presenter. 291 mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 292 293 boolean isSelf = Sms.isOutgoingFolder(mMessageItem.mBoxId); 294 String addr = isSelf ? null : mMessageItem.mAddress; 295 updateAvatarView(addr, isSelf); 296 297 // Get and/or lazily set the formatted message from/on the 298 // MessageItem. Because the MessageItem instances come from a 299 // cache (currently of size ~50), the hit rate on avoiding the 300 // expensive formatMessage() call is very high. 301 CharSequence formattedMessage = mMessageItem.getCachedFormattedMessage(); 302 if (formattedMessage == null) { 303 formattedMessage = formatMessage(mMessageItem, mMessageItem.mContact, 304 mMessageItem.mBody, 305 mMessageItem.mSubject, 306 mMessageItem.mHighlight, 307 mMessageItem.mTextContentType); 308 } 309 mBodyTextView.setText(formattedMessage); 310 311 // Debugging code to put the URI of the image attachment in the body of the list item. 312 if (DEBUG) { 313 String debugText = null; 314 if (mMessageItem.mSlideshow == null) { 315 debugText = "NULL slideshow"; 316 } else { 317 SlideModel slide = ((SlideshowModel) mMessageItem.mSlideshow).get(0); 318 if (slide == null) { 319 debugText = "NULL first slide"; 320 } else if (!slide.hasImage()) { 321 debugText = "Not an image"; 322 } else { 323 debugText = slide.getImage().getUri().toString(); 324 } 325 } 326 mBodyTextView.setText(debugText); 327 } 328 329 // If we're in the process of sending a message (i.e. pending), then we show a "SENDING..." 330 // string in place of the timestamp. 331 mDateView.setText(mMessageItem.isSending() ? 332 mContext.getResources().getString(R.string.sending_message) : 333 mMessageItem.mTimestamp); 334 335 if (mMessageItem.isSms()) { 336 hideMmsViewIfNeeded(); 337 mMessageItem.setOnPduLoaded(null); 338 } else { 339 if (mMessageItem.mSlideshow != null) { 340 if (mPresenter == null) { 341 mPresenter = PresenterFactory.getPresenter( 342 "MmsThumbnailPresenter", mContext, 343 this, mMessageItem.mSlideshow); 344 } else { 345 mPresenter.setModel(mMessageItem.mSlideshow); 346 mPresenter.setView(this); 347 } 348 mMessageItem.setOnPduLoaded(new MessageItem.PduLoadedCallback() { 349 public void onPduLoaded(MessageItem messageItem) { 350 if (messageItem.getMessageId() == mMessageItem.getMessageId()) { 351 MessageListItem.this.invalidate(); 352 } 353 } 354 }); 355 mPresenter.present(new ImageLoadedCallback(this)); 356 } 357 358 if (mMessageItem.mAttachmentType != WorkingMessage.TEXT) { 359 inflateMmsView(); 360 mMmsView.setVisibility(View.VISIBLE); 361 setOnClickListener(mMessageItem); 362 drawPlaybackButton(mMessageItem); 363 } else { 364 hideMmsViewIfNeeded(); 365 } 366 } 367 drawRightStatusIndicator(mMessageItem); 368 369 requestLayout(); 370 } 371 372 static private class ImageLoadedCallback implements ItemLoadedCallback<ImageLoaded> { 373 private final long mMessageId; 374 private final MessageListItem mListItem; 375 376 public ImageLoadedCallback(MessageListItem listItem) { 377 mListItem = listItem; 378 mMessageId = listItem.getMessageItem().getMessageId(); 379 } 380 381 public void onItemLoaded(ImageLoaded imageLoaded, Throwable exception) { 382 // Make sure we're still pointing to the same message. The list item could have 383 // been recycled. 384 MessageItem msgItem = mListItem.mMessageItem; 385 if (msgItem != null && msgItem.getMessageId() == mMessageId) { 386 if (imageLoaded.mIsVideo) { 387 mListItem.setVideoThumbnail(null, imageLoaded.mBitmap); 388 } else { 389 mListItem.setImage(null, imageLoaded.mBitmap); 390 } 391 } 392 } 393 } 394 395 private void hideMmsViewIfNeeded() { 396 if (mMmsView != null) { 397 mMmsView.setVisibility(View.GONE); 398 } 399 } 400 401 @Override 402 public void startAudio() { 403 // TODO Auto-generated method stub 404 } 405 406 @Override 407 public void startVideo() { 408 // TODO Auto-generated method stub 409 } 410 411 @Override 412 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 413 // TODO Auto-generated method stub 414 } 415 416 @Override 417 public void setImage(String name, Bitmap bitmap) { 418 inflateMmsView(); 419 420 try { 421 if (null == bitmap) { 422 bitmap = BitmapFactory.decodeResource(getResources(), 423 R.drawable.ic_missing_thumbnail_picture); 424 } 425 mImageView.setImageBitmap(bitmap); 426 mImageView.setVisibility(VISIBLE); 427 } catch (java.lang.OutOfMemoryError e) { 428 Log.e(TAG, "setImage: out of memory: ", e); 429 } 430 } 431 432 private void inflateMmsView() { 433 if (mMmsView == null) { 434 //inflate the surrounding view_stub 435 findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE); 436 437 mMmsView = findViewById(R.id.mms_view); 438 mImageView = (ImageView) findViewById(R.id.image_view); 439 mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); 440 } 441 } 442 443 private void inflateDownloadControls() { 444 if (mDownloadButton == null) { 445 //inflate the download controls 446 findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); 447 mDownloadButton = (Button) findViewById(R.id.btn_download_msg); 448 mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); 449 } 450 } 451 452 453 private LineHeightSpan mSpan = new LineHeightSpan() { 454 @Override 455 public void chooseHeight(CharSequence text, int start, 456 int end, int spanstartv, int v, FontMetricsInt fm) { 457 fm.ascent -= 10; 458 } 459 }; 460 461 TextAppearanceSpan mTextSmallSpan = 462 new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Small); 463 464 ForegroundColorSpan mColorSpan = null; // set in ctor 465 466 private CharSequence formatMessage(MessageItem msgItem, String contact, String body, 467 String subject, Pattern highlight, 468 String contentType) { 469 SpannableStringBuilder buf = new SpannableStringBuilder(); 470 471 boolean hasSubject = !TextUtils.isEmpty(subject); 472 SmileyParser parser = SmileyParser.getInstance(); 473 if (hasSubject) { 474 CharSequence smilizedSubject = parser.addSmileySpans(subject); 475 // Can't use the normal getString() with extra arguments for string replacement 476 // because it doesn't preserve the SpannableText returned by addSmileySpans. 477 // We have to manually replace the %s with our text. 478 buf.append(TextUtils.replace(mContext.getResources().getString(R.string.inline_subject), 479 new String[] { "%s" }, new CharSequence[] { smilizedSubject })); 480 } 481 482 if (!TextUtils.isEmpty(body)) { 483 // Converts html to spannable if ContentType is "text/html". 484 if (contentType != null && ContentType.TEXT_HTML.equals(contentType)) { 485 buf.append("\n"); 486 buf.append(Html.fromHtml(body)); 487 } else { 488 if (hasSubject) { 489 buf.append(" - "); 490 } 491 buf.append(parser.addSmileySpans(body)); 492 } 493 } 494 495 if (highlight != null) { 496 Matcher m = highlight.matcher(buf.toString()); 497 while (m.find()) { 498 buf.setSpan(new StyleSpan(Typeface.BOLD), m.start(), m.end(), 0); 499 } 500 } 501 return buf; 502 } 503 504 private void drawPlaybackButton(MessageItem msgItem) { 505 switch (msgItem.mAttachmentType) { 506 case WorkingMessage.SLIDESHOW: 507 case WorkingMessage.AUDIO: 508 case WorkingMessage.VIDEO: 509 // Show the 'Play' button and bind message info on it. 510 mSlideShowButton.setTag(msgItem); 511 // Set call-back for the 'Play' button. 512 mSlideShowButton.setOnClickListener(this); 513 mSlideShowButton.setVisibility(View.VISIBLE); 514 setLongClickable(true); 515 516 // When we show the mSlideShowButton, this list item's onItemClickListener doesn't 517 // get called. (It gets set in ComposeMessageActivity: 518 // mMsgListView.setOnItemClickListener) Here we explicitly set the item's 519 // onClickListener. It allows the item to respond to embedded html links and at the 520 // same time, allows the slide show play button to work. 521 setOnClickListener(new OnClickListener() { 522 @Override 523 public void onClick(View v) { 524 onMessageListItemClick(); 525 } 526 }); 527 break; 528 default: 529 mSlideShowButton.setVisibility(View.GONE); 530 break; 531 } 532 } 533 534 // OnClick Listener for the playback button 535 @Override 536 public void onClick(View v) { 537 MessageItem mi = (MessageItem) v.getTag(); 538 switch (mi.mAttachmentType) { 539 case WorkingMessage.VIDEO: 540 case WorkingMessage.AUDIO: 541 case WorkingMessage.SLIDESHOW: 542 MessageUtils.viewMmsMessageAttachment(mContext, mi.mMessageUri, mi.mSlideshow); 543 break; 544 } 545 } 546 547 public void onMessageListItemClick() { 548 // If the message is a failed one, clicking it should reload it in the compose view, 549 // regardless of whether it has links in it 550 if (mMessageItem != null && 551 mMessageItem.isOutgoingMessage() && 552 mMessageItem.isFailedMessage() ) { 553 recomposeFailedMessage(); 554 return; 555 } 556 557 // Check for links. If none, do nothing; if 1, open it; if >1, ask user to pick one 558 final URLSpan[] spans = mBodyTextView.getUrls(); 559 560 if (spans.length == 0) { 561 // Do nothing. 562 } else if (spans.length == 1) { 563 spans[0].onClick(mBodyTextView); 564 } else { 565 ArrayAdapter<URLSpan> adapter = 566 new ArrayAdapter<URLSpan>(mContext, android.R.layout.select_dialog_item, spans) { 567 @Override 568 public View getView(int position, View convertView, ViewGroup parent) { 569 View v = super.getView(position, convertView, parent); 570 try { 571 URLSpan span = getItem(position); 572 String url = span.getURL(); 573 Uri uri = Uri.parse(url); 574 TextView tv = (TextView) v; 575 Drawable d = mContext.getPackageManager().getActivityIcon( 576 new Intent(Intent.ACTION_VIEW, uri)); 577 if (d != null) { 578 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 579 tv.setCompoundDrawablePadding(10); 580 tv.setCompoundDrawables(d, null, null, null); 581 } 582 final String telPrefix = "tel:"; 583 if (url.startsWith(telPrefix)) { 584 url = PhoneNumberUtils.formatNumber( 585 url.substring(telPrefix.length()), mDefaultCountryIso); 586 } 587 tv.setText(url); 588 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 589 // it's ok if we're unable to set the drawable for this view - the user 590 // can still use it 591 } 592 return v; 593 } 594 }; 595 596 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 597 598 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 599 @Override 600 public final void onClick(DialogInterface dialog, int which) { 601 if (which >= 0) { 602 spans[which].onClick(mBodyTextView); 603 } 604 dialog.dismiss(); 605 } 606 }; 607 608 b.setTitle(R.string.select_link_title); 609 b.setCancelable(true); 610 b.setAdapter(adapter, click); 611 612 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 613 @Override 614 public final void onClick(DialogInterface dialog, int which) { 615 dialog.dismiss(); 616 } 617 }); 618 619 b.show(); 620 } 621 } 622 623 private void setOnClickListener(final MessageItem msgItem) { 624 switch(msgItem.mAttachmentType) { 625 case WorkingMessage.IMAGE: 626 case WorkingMessage.VIDEO: 627 mImageView.setOnClickListener(new OnClickListener() { 628 @Override 629 public void onClick(View v) { 630 MessageUtils.viewMmsMessageAttachment(mContext, null, msgItem.mSlideshow); 631 } 632 }); 633 mImageView.setOnLongClickListener(new OnLongClickListener() { 634 @Override 635 public boolean onLongClick(View v) { 636 return v.showContextMenu(); 637 } 638 }); 639 break; 640 641 default: 642 mImageView.setOnClickListener(null); 643 break; 644 } 645 } 646 647 /** 648 * Assuming the current message is a failed one, reload it into the compose view so that the 649 * user can resend it. 650 */ 651 private void recomposeFailedMessage() { 652 String type = mMessageItem.mType; 653 final int what; 654 if (type.equals("sms")) { 655 what = MSG_LIST_EDIT_SMS; 656 } else { 657 what = MSG_LIST_EDIT_MMS; 658 } 659 if (null != mHandler) { 660 Message msg = Message.obtain(mHandler, what); 661 msg.obj = new Long(mMessageItem.mMsgId); 662 msg.sendToTarget(); 663 } 664 } 665 666 private void drawRightStatusIndicator(MessageItem msgItem) { 667 // Locked icon 668 if (msgItem.mLocked) { 669 mLockedIndicator.setImageResource(R.drawable.ic_lock_message_sms); 670 mLockedIndicator.setVisibility(View.VISIBLE); 671 } else { 672 mLockedIndicator.setVisibility(View.GONE); 673 } 674 675 // Delivery icon - we can show a failed icon for both sms and mms, but for an actual 676 // delivery, we only show the icon for sms. We don't have the information here in mms to 677 // know whether the message has been delivered. For mms, msgItem.mDeliveryStatus set 678 // to MessageItem.DeliveryStatus.RECEIVED simply means the setting requesting a 679 // delivery report was turned on when the message was sent. Yes, it's confusing! 680 if ((msgItem.isOutgoingMessage() && msgItem.isFailedMessage()) || 681 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) { 682 mDeliveredIndicator.setImageResource(R.drawable.ic_list_alert_sms_failed); 683 mDeliveredIndicator.setVisibility(View.VISIBLE); 684 } else if (msgItem.isSms() && 685 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) { 686 mDeliveredIndicator.setImageResource(R.drawable.ic_sms_mms_delivered); 687 mDeliveredIndicator.setVisibility(View.VISIBLE); 688 } else { 689 mDeliveredIndicator.setVisibility(View.GONE); 690 } 691 692 // Message details icon - this icon is shown both for sms and mms messages. For mms, 693 // we show the icon if the read report or delivery report setting was set when the 694 // message was sent. Showing the icon tells the user there's more information 695 // by selecting the "View report" menu. 696 if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport 697 || (msgItem.isMms() && 698 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED)) { 699 mDetailsIndicator.setImageResource(R.drawable.ic_sms_mms_details); 700 mDetailsIndicator.setVisibility(View.VISIBLE); 701 } else { 702 mDetailsIndicator.setVisibility(View.GONE); 703 } 704 } 705 706 @Override 707 public void setImageRegionFit(String fit) { 708 // TODO Auto-generated method stub 709 } 710 711 @Override 712 public void setImageVisibility(boolean visible) { 713 // TODO Auto-generated method stub 714 } 715 716 @Override 717 public void setText(String name, String text) { 718 // TODO Auto-generated method stub 719 } 720 721 @Override 722 public void setTextVisibility(boolean visible) { 723 // TODO Auto-generated method stub 724 } 725 726 @Override 727 public void setVideo(String name, Uri uri) { 728 } 729 730 @Override 731 public void setVideoThumbnail(String name, Bitmap bitmap) { 732 inflateMmsView(); 733 734 try { 735 mImageView.setImageBitmap(bitmap); 736 mImageView.setVisibility(VISIBLE); 737 } catch (java.lang.OutOfMemoryError e) { 738 Log.e(TAG, "setVideo: out of memory: ", e); 739 } 740 } 741 742 @Override 743 public void setVideoVisibility(boolean visible) { 744 // TODO Auto-generated method stub 745 } 746 747 @Override 748 public void stopAudio() { 749 // TODO Auto-generated method stub 750 } 751 752 @Override 753 public void stopVideo() { 754 // TODO Auto-generated method stub 755 } 756 757 @Override 758 public void reset() { 759 if (mImageView != null) { 760 mImageView.setVisibility(GONE); 761 } 762 } 763 764 @Override 765 public void setVisibility(boolean visible) { 766 // TODO Auto-generated method stub 767 } 768 769 @Override 770 public void pauseAudio() { 771 // TODO Auto-generated method stub 772 773 } 774 775 @Override 776 public void pauseVideo() { 777 // TODO Auto-generated method stub 778 779 } 780 781 @Override 782 public void seekAudio(int seekTo) { 783 // TODO Auto-generated method stub 784 785 } 786 787 @Override 788 public void seekVideo(int seekTo) { 789 // TODO Auto-generated method stub 790 791 } 792 793 /** 794 * Override dispatchDraw so that we can put our own background and border in. 795 * This is all complexity to support a shared border from one item to the next. 796 */ 797 @Override 798 public void dispatchDraw(Canvas c) { 799 View v = mMessageBlock; 800 if (v != null) { 801 float l = v.getX(); 802 float t = v.getY(); 803 float r = v.getX() + v.getWidth(); 804 float b = v.getY() + v.getHeight(); 805 806 Path path = mPath; 807 path.reset(); 808 809 super.dispatchDraw(c); 810 811 path.reset(); 812 813 r -= 1; 814 815 // This block of code draws the border around the "message block" section 816 // of the layout. This would normally be a simple rectangle but we omit 817 // the border at the point of the avatar's divot. Also, the bottom is drawn 818 // 1 pixel below our own bounds to get it to line up with the border of 819 // the next item. 820 // 821 // But for the last item we draw the bottom in our own bounds -- so it will 822 // show up. 823 if (mIsLastItemInList) { 824 b -= 1; 825 } 826 if (mAvatar.getPosition() == Divot.RIGHT_UPPER) { 827 path.moveTo(l, t + mAvatar.getCloseOffset()); 828 path.lineTo(l, t); 829 path.lineTo(r, t); 830 path.lineTo(r, b); 831 path.lineTo(l, b); 832 path.lineTo(l, t + mAvatar.getFarOffset()); 833 } else if (mAvatar.getPosition() == Divot.LEFT_UPPER) { 834 path.moveTo(r, t + mAvatar.getCloseOffset()); 835 path.lineTo(r, t); 836 path.lineTo(l, t); 837 path.lineTo(l, b); 838 path.lineTo(r, b); 839 path.lineTo(r, t + mAvatar.getFarOffset()); 840 } 841 842 Paint paint = mPaint; 843// paint.setColor(0xff00ff00); 844 paint.setColor(0xffcccccc); 845 paint.setStrokeWidth(1F); 846 paint.setStyle(Paint.Style.STROKE); 847 c.drawPath(path, paint); 848 } else { 849 super.dispatchDraw(c); 850 } 851 } 852} 853