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