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