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