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