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