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.Paint.FontMetricsInt; 30 import android.graphics.Typeface; 31 import android.graphics.drawable.Drawable; 32 import android.net.Uri; 33 import android.os.Handler; 34 import android.os.Message; 35 import android.provider.ContactsContract.Profile; 36 import android.provider.Telephony.Sms; 37 import android.telephony.PhoneNumberUtils; 38 import android.telephony.TelephonyManager; 39 import android.text.Html; 40 import android.text.SpannableStringBuilder; 41 import android.text.TextUtils; 42 import android.text.method.HideReturnsTransformationMethod; 43 import android.text.style.ForegroundColorSpan; 44 import android.text.style.LineHeightSpan; 45 import android.text.style.StyleSpan; 46 import android.text.style.TextAppearanceSpan; 47 import android.text.style.URLSpan; 48 import android.util.AttributeSet; 49 import android.util.Log; 50 import android.view.View; 51 import android.view.View.OnClickListener; 52 import android.view.ViewGroup; 53 import android.widget.ArrayAdapter; 54 import android.widget.Button; 55 import android.widget.ImageButton; 56 import android.widget.ImageView; 57 import android.widget.LinearLayout; 58 import android.widget.TextView; 59 60 import com.android.mms.MmsApp; 61 import com.android.mms.R; 62 import com.android.mms.data.Contact; 63 import com.android.mms.data.WorkingMessage; 64 import com.android.mms.model.SlideModel; 65 import com.android.mms.model.SlideshowModel; 66 import com.android.mms.transaction.Transaction; 67 import com.android.mms.transaction.TransactionBundle; 68 import com.android.mms.transaction.TransactionService; 69 import com.android.mms.util.DownloadManager; 70 import com.android.mms.util.ItemLoadedCallback; 71 import com.android.mms.util.ThumbnailManager.ImageLoaded; 72 import com.google.android.mms.ContentType; 73 import com.google.android.mms.pdu.PduHeaders; 74 75 /** 76 * This class provides view of a message in the messages list. 77 */ 78 public class MessageListItem extends LinearLayout implements 79 SlideViewInterface, OnClickListener { 80 public static final String EXTRA_URLS = "com.android.mms.ExtraUrls"; 81 82 private static final String TAG = "MessageListItem"; 83 private static final boolean DEBUG = false; 84 private static final boolean DEBUG_DONT_LOAD_IMAGES = false; 85 86 static final int MSG_LIST_EDIT = 1; 87 static final int MSG_LIST_PLAY = 2; 88 static final int MSG_LIST_DETAILS = 3; 89 90 private View mMmsView; 91 private ImageView mImageView; 92 private ImageView mLockedIndicator; 93 private ImageView mDeliveredIndicator; 94 private ImageView mDetailsIndicator; 95 private ImageButton mSlideShowButton; 96 private TextView mBodyTextView; 97 private Button mDownloadButton; 98 private TextView mDownloadingLabel; 99 private Handler mHandler; 100 private MessageItem mMessageItem; 101 private String mDefaultCountryIso; 102 private TextView mDateView; 103 public View mMessageBlock; 104 private QuickContactDivot mAvatar; 105 static private Drawable sDefaultContactImage; 106 private Presenter mPresenter; 107 private int mPosition; // for debugging 108 private ImageLoadedCallback mImageLoadedCallback; 109 private boolean mMultiRecipients; 110 111 public MessageListItem(Context context) { 112 super(context); 113 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 114 115 if (sDefaultContactImage == null) { 116 sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture); 117 } 118 } 119 120 public MessageListItem(Context context, AttributeSet attrs) { 121 super(context, attrs); 122 123 int color = mContext.getResources().getColor(R.color.timestamp_color); 124 mColorSpan = new ForegroundColorSpan(color); 125 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 126 127 if (sDefaultContactImage == null) { 128 sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture); 129 } 130 } 131 132 @Override 133 protected void onFinishInflate() { 134 super.onFinishInflate(); 135 136 mBodyTextView = (TextView) findViewById(R.id.text_view); 137 mDateView = (TextView) findViewById(R.id.date_view); 138 mLockedIndicator = (ImageView) findViewById(R.id.locked_indicator); 139 mDeliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator); 140 mDetailsIndicator = (ImageView) findViewById(R.id.details_indicator); 141 mAvatar = (QuickContactDivot) findViewById(R.id.avatar); 142 mMessageBlock = findViewById(R.id.message_block); 143 } 144 145 public void bind(MessageItem msgItem, boolean convHasMultiRecipients, int position) { 146 if (DEBUG) { 147 Log.v(TAG, "bind for item: " + position + " old: " + 148 (mMessageItem != null ? mMessageItem.toString() : "NULL" ) + 149 " new " + msgItem.toString()); 150 } 151 boolean sameItem = mMessageItem != null && mMessageItem.mMsgId == msgItem.mMsgId; 152 mMessageItem = msgItem; 153 154 mPosition = position; 155 mMultiRecipients = convHasMultiRecipients; 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(sameItem); 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 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 showMmsView(false); 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, null, 207 mMessageItem.mSubject, 208 mMessageItem.mHighlight, 209 mMessageItem.mTextContentType)); 210 211 mDateView.setText(buildTimestampLine(msgSizeText + " " + mMessageItem.mTimestamp)); 212 213 switch (mMessageItem.getMmsDownloadStatus()) { 214 case DownloadManager.STATE_PRE_DOWNLOADING: 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 DownloadManager.getInstance().markState( 251 mMessageItem.mMessageUri, DownloadManager.STATE_PRE_DOWNLOADING); 252 } 253 }); 254 break; 255 } 256 257 // Hide the indicators. 258 mLockedIndicator.setVisibility(View.GONE); 259 mDeliveredIndicator.setVisibility(View.GONE); 260 mDetailsIndicator.setVisibility(View.GONE); 261 updateAvatarView(mMessageItem.mAddress, false); 262 } 263 264 private String buildTimestampLine(String timestamp) { 265 if (!mMultiRecipients || mMessageItem.isMe() || TextUtils.isEmpty(mMessageItem.mContact)) { 266 // Never show "Me" for messages I sent. 267 return timestamp; 268 } 269 // This is a group conversation, show the sender's name on the same line as the timestamp. 270 return mContext.getString(R.string.message_timestamp_format, mMessageItem.mContact, 271 timestamp); 272 } 273 274 private void showDownloadingAttachment() { 275 inflateDownloadControls(); 276 mDownloadingLabel.setVisibility(View.VISIBLE); 277 mDownloadButton.setVisibility(View.GONE); 278 } 279 280 private void updateAvatarView(String addr, boolean isSelf) { 281 Drawable avatarDrawable; 282 if (isSelf || !TextUtils.isEmpty(addr)) { 283 Contact contact = isSelf ? Contact.getMe(false) : Contact.get(addr, false); 284 avatarDrawable = contact.getAvatar(mContext, sDefaultContactImage); 285 286 if (isSelf) { 287 mAvatar.assignContactUri(Profile.CONTENT_URI); 288 } else { 289 if (contact.existsInDatabase()) { 290 mAvatar.assignContactUri(contact.getUri()); 291 } else { 292 mAvatar.assignContactFromPhone(contact.getNumber(), true); 293 } 294 } 295 } else { 296 avatarDrawable = sDefaultContactImage; 297 } 298 mAvatar.setImageDrawable(avatarDrawable); 299 } 300 301 private void bindCommonMessage(final boolean sameItem) { 302 if (mDownloadButton != null) { 303 mDownloadButton.setVisibility(View.GONE); 304 mDownloadingLabel.setVisibility(View.GONE); 305 } 306 // Since the message text should be concatenated with the sender's 307 // address(or name), I have to display it here instead of 308 // displaying it by the Presenter. 309 mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 310 311 boolean haveLoadedPdu = mMessageItem.isSms() || mMessageItem.mSlideshow != null; 312 // Here we're avoiding reseting the avatar to the empty avatar when we're rebinding 313 // to the same item. This happens when there's a DB change which causes the message item 314 // cache in the MessageListAdapter to get cleared. When an mms MessageItem is newly 315 // created, it has no info in it except the message id. The info is eventually loaded 316 // and bindCommonMessage is called again (see onPduLoaded below). When we haven't loaded 317 // the pdu, we don't want to call updateAvatarView because it 318 // will set the avatar to the generic avatar then when this method is called again 319 // from onPduLoaded, it will reset to the real avatar. This test is to avoid that flash. 320 if (!sameItem || haveLoadedPdu) { 321 boolean isSelf = Sms.isOutgoingFolder(mMessageItem.mBoxId); 322 String addr = isSelf ? null : mMessageItem.mAddress; 323 updateAvatarView(addr, isSelf); 324 } 325 326 // Get and/or lazily set the formatted message from/on the 327 // MessageItem. Because the MessageItem instances come from a 328 // cache (currently of size ~50), the hit rate on avoiding the 329 // expensive formatMessage() call is very high. 330 CharSequence formattedMessage = mMessageItem.getCachedFormattedMessage(); 331 if (formattedMessage == null) { 332 formattedMessage = formatMessage(mMessageItem, 333 mMessageItem.mBody, 334 mMessageItem.mSubject, 335 mMessageItem.mHighlight, 336 mMessageItem.mTextContentType); 337 mMessageItem.setCachedFormattedMessage(formattedMessage); 338 } 339 if (!sameItem || haveLoadedPdu) { 340 mBodyTextView.setText(formattedMessage); 341 } 342 343 // Debugging code to put the URI of the image attachment in the body of the list item. 344 if (DEBUG) { 345 String debugText = null; 346 if (mMessageItem.mSlideshow == null) { 347 debugText = "NULL slideshow"; 348 } else { 349 SlideModel slide = mMessageItem.mSlideshow.get(0); 350 if (slide == null) { 351 debugText = "NULL first slide"; 352 } else if (!slide.hasImage()) { 353 debugText = "Not an image"; 354 } else { 355 debugText = slide.getImage().getUri().toString(); 356 } 357 } 358 mBodyTextView.setText(mPosition + ": " + debugText); 359 } 360 361 // If we're in the process of sending a message (i.e. pending), then we show a "SENDING..." 362 // string in place of the timestamp. 363 if (!sameItem || haveLoadedPdu) { 364 mDateView.setText(buildTimestampLine(mMessageItem.isSending() ? 365 mContext.getResources().getString(R.string.sending_message) : 366 mMessageItem.mTimestamp)); 367 } 368 if (mMessageItem.isSms()) { 369 showMmsView(false); 370 mMessageItem.setOnPduLoaded(null); 371 } else { 372 if (DEBUG) { 373 Log.v(TAG, "bindCommonMessage for item: " + mPosition + " " + 374 mMessageItem.toString() + 375 " mMessageItem.mAttachmentType: " + mMessageItem.mAttachmentType + 376 " sameItem: " + sameItem); 377 } 378 if (mMessageItem.mAttachmentType != WorkingMessage.TEXT) { 379 if (!sameItem) { 380 setImage(null, null); 381 } 382 setOnClickListener(mMessageItem); 383 drawPlaybackButton(mMessageItem); 384 } else { 385 showMmsView(false); 386 } 387 if (mMessageItem.mSlideshow == null) { 388 mMessageItem.setOnPduLoaded(new MessageItem.PduLoadedCallback() { 389 public void onPduLoaded(MessageItem messageItem) { 390 if (DEBUG) { 391 Log.v(TAG, "PduLoadedCallback in MessageListItem for item: " + mPosition + 392 " " + (mMessageItem == null ? "NULL" : mMessageItem.toString()) + 393 " passed in item: " + 394 (messageItem == null ? "NULL" : messageItem.toString())); 395 } 396 if (messageItem != null && mMessageItem != null && 397 messageItem.getMessageId() == mMessageItem.getMessageId()) { 398 mMessageItem.setCachedFormattedMessage(null); 399 bindCommonMessage(true); 400 } 401 } 402 }); 403 } else { 404 if (mPresenter == null) { 405 mPresenter = PresenterFactory.getPresenter( 406 "MmsThumbnailPresenter", mContext, 407 this, mMessageItem.mSlideshow); 408 } else { 409 mPresenter.setModel(mMessageItem.mSlideshow); 410 mPresenter.setView(this); 411 } 412 if (mImageLoadedCallback == null) { 413 mImageLoadedCallback = new ImageLoadedCallback(this); 414 } else { 415 mImageLoadedCallback.reset(this); 416 } 417 mPresenter.present(mImageLoadedCallback); 418 } 419 } 420 drawRightStatusIndicator(mMessageItem); 421 422 requestLayout(); 423 } 424 425 static private class ImageLoadedCallback implements ItemLoadedCallback<ImageLoaded> { 426 private long mMessageId; 427 private final MessageListItem mListItem; 428 429 public ImageLoadedCallback(MessageListItem listItem) { 430 mListItem = listItem; 431 mMessageId = listItem.getMessageItem().getMessageId(); 432 } 433 434 public void reset(MessageListItem listItem) { 435 mMessageId = listItem.getMessageItem().getMessageId(); 436 } 437 438 public void onItemLoaded(ImageLoaded imageLoaded, Throwable exception) { 439 if (DEBUG_DONT_LOAD_IMAGES) { 440 return; 441 } 442 // Make sure we're still pointing to the same message. The list item could have 443 // been recycled. 444 MessageItem msgItem = mListItem.mMessageItem; 445 if (msgItem != null && msgItem.getMessageId() == mMessageId) { 446 if (imageLoaded.mIsVideo) { 447 mListItem.setVideoThumbnail(null, imageLoaded.mBitmap); 448 } else { 449 mListItem.setImage(null, imageLoaded.mBitmap); 450 } 451 } 452 } 453 } 454 455 @Override 456 public void startAudio() { 457 // TODO Auto-generated method stub 458 } 459 460 @Override 461 public void startVideo() { 462 // TODO Auto-generated method stub 463 } 464 465 @Override 466 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 467 // TODO Auto-generated method stub 468 } 469 470 @Override 471 public void setImage(String name, Bitmap bitmap) { 472 showMmsView(true); 473 474 try { 475 mImageView.setImageBitmap(bitmap); 476 mImageView.setVisibility(VISIBLE); 477 } catch (java.lang.OutOfMemoryError e) { 478 Log.e(TAG, "setImage: out of memory: ", e); 479 } 480 } 481 482 private void showMmsView(boolean visible) { 483 if (mMmsView == null) { 484 mMmsView = findViewById(R.id.mms_view); 485 // if mMmsView is still null here, that mean the mms section hasn't been inflated 486 487 if (visible && mMmsView == null) { 488 //inflate the mms view_stub 489 View mmsStub = findViewById(R.id.mms_layout_view_stub); 490 mmsStub.setVisibility(View.VISIBLE); 491 mMmsView = findViewById(R.id.mms_view); 492 } 493 } 494 if (mMmsView != null) { 495 if (mImageView == null) { 496 mImageView = (ImageView) findViewById(R.id.image_view); 497 } 498 if (mSlideShowButton == null) { 499 mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); 500 } 501 mMmsView.setVisibility(visible ? View.VISIBLE : View.GONE); 502 mImageView.setVisibility(visible ? View.VISIBLE : View.GONE); 503 } 504 } 505 506 private void inflateDownloadControls() { 507 if (mDownloadButton == null) { 508 //inflate the download controls 509 findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); 510 mDownloadButton = (Button) findViewById(R.id.btn_download_msg); 511 mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); 512 } 513 } 514 515 516 private LineHeightSpan mSpan = new LineHeightSpan() { 517 @Override 518 public void chooseHeight(CharSequence text, int start, 519 int end, int spanstartv, int v, FontMetricsInt fm) { 520 fm.ascent -= 10; 521 } 522 }; 523 524 TextAppearanceSpan mTextSmallSpan = 525 new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Small); 526 527 ForegroundColorSpan mColorSpan = null; // set in ctor 528 529 private CharSequence formatMessage(MessageItem msgItem, String body, 530 String subject, Pattern highlight, 531 String contentType) { 532 SpannableStringBuilder buf = new SpannableStringBuilder(); 533 534 boolean hasSubject = !TextUtils.isEmpty(subject); 535 if (hasSubject) { 536 buf.append(mContext.getResources().getString(R.string.inline_subject, subject)); 537 } 538 539 if (!TextUtils.isEmpty(body)) { 540 // Converts html to spannable if ContentType is "text/html". 541 if (contentType != null && ContentType.TEXT_HTML.equals(contentType)) { 542 buf.append("\n"); 543 buf.append(Html.fromHtml(body)); 544 } else { 545 if (hasSubject) { 546 buf.append(" - "); 547 } 548 buf.append(body); 549 } 550 } 551 552 if (highlight != null) { 553 Matcher m = highlight.matcher(buf.toString()); 554 while (m.find()) { 555 buf.setSpan(new StyleSpan(Typeface.BOLD), m.start(), m.end(), 0); 556 } 557 } 558 return buf; 559 } 560 561 private void drawPlaybackButton(MessageItem msgItem) { 562 switch (msgItem.mAttachmentType) { 563 case WorkingMessage.SLIDESHOW: 564 case WorkingMessage.AUDIO: 565 case WorkingMessage.VIDEO: 566 // Show the 'Play' button and bind message info on it. 567 mSlideShowButton.setTag(msgItem); 568 // Set call-back for the 'Play' button. 569 mSlideShowButton.setOnClickListener(this); 570 mSlideShowButton.setVisibility(View.VISIBLE); 571 setLongClickable(true); 572 573 // When we show the mSlideShowButton, this list item's onItemClickListener doesn't 574 // get called. (It gets set in ComposeMessageActivity: 575 // mMsgListView.setOnItemClickListener) Here we explicitly set the item's 576 // onClickListener. It allows the item to respond to embedded html links and at the 577 // same time, allows the slide show play button to work. 578 setOnClickListener(new OnClickListener() { 579 @Override 580 public void onClick(View v) { 581 onMessageListItemClick(); 582 } 583 }); 584 break; 585 default: 586 mSlideShowButton.setVisibility(View.GONE); 587 break; 588 } 589 } 590 591 // OnClick Listener for the playback button 592 @Override 593 public void onClick(View v) { 594 sendMessage(mMessageItem, MSG_LIST_PLAY); 595 } 596 597 private void sendMessage(MessageItem messageItem, int message) { 598 if (mHandler != null) { 599 Message msg = Message.obtain(mHandler, message); 600 msg.obj = messageItem; 601 msg.sendToTarget(); // See ComposeMessageActivity.mMessageListItemHandler.handleMessage 602 } 603 } 604 605 public void onMessageListItemClick() { 606 // If the message is a failed one, clicking it should reload it in the compose view, 607 // regardless of whether it has links in it 608 if (mMessageItem != null && 609 mMessageItem.isOutgoingMessage() && 610 mMessageItem.isFailedMessage() ) { 611 612 // Assuming the current message is a failed one, reload it into the compose view so 613 // the user can resend it. 614 sendMessage(mMessageItem, MSG_LIST_EDIT); 615 return; 616 } 617 618 // Check for links. If none, do nothing; if 1, open it; if >1, ask user to pick one 619 final URLSpan[] spans = mBodyTextView.getUrls(); 620 621 if (spans.length == 0) { 622 sendMessage(mMessageItem, MSG_LIST_DETAILS); // show the message details dialog 623 } else if (spans.length == 1) { 624 spans[0].onClick(mBodyTextView); 625 } else { 626 ArrayAdapter<URLSpan> adapter = 627 new ArrayAdapter<URLSpan>(mContext, android.R.layout.select_dialog_item, spans) { 628 @Override 629 public View getView(int position, View convertView, ViewGroup parent) { 630 View v = super.getView(position, convertView, parent); 631 try { 632 URLSpan span = getItem(position); 633 String url = span.getURL(); 634 Uri uri = Uri.parse(url); 635 TextView tv = (TextView) v; 636 Drawable d = mContext.getPackageManager().getActivityIcon( 637 new Intent(Intent.ACTION_VIEW, uri)); 638 if (d != null) { 639 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 640 tv.setCompoundDrawablePadding(10); 641 tv.setCompoundDrawables(d, null, null, null); 642 } 643 final String telPrefix = "tel:"; 644 if (url.startsWith(telPrefix)) { 645 if ((mDefaultCountryIso == null) || mDefaultCountryIso.isEmpty()) { 646 url = url.substring(telPrefix.length()); 647 } 648 else { 649 url = PhoneNumberUtils.formatNumber( 650 url.substring(telPrefix.length()), mDefaultCountryIso); 651 } 652 } 653 tv.setText(url); 654 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 655 // it's ok if we're unable to set the drawable for this view - the user 656 // can still use it 657 } 658 return v; 659 } 660 }; 661 662 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 663 664 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 665 @Override 666 public final void onClick(DialogInterface dialog, int which) { 667 if (which >= 0) { 668 spans[which].onClick(mBodyTextView); 669 } 670 dialog.dismiss(); 671 } 672 }; 673 674 b.setTitle(R.string.select_link_title); 675 b.setCancelable(true); 676 b.setAdapter(adapter, click); 677 678 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 679 @Override 680 public final void onClick(DialogInterface dialog, int which) { 681 dialog.dismiss(); 682 } 683 }); 684 685 b.show(); 686 } 687 } 688 689 private void setOnClickListener(final MessageItem msgItem) { 690 switch(msgItem.mAttachmentType) { 691 case WorkingMessage.IMAGE: 692 case WorkingMessage.VIDEO: 693 mImageView.setOnClickListener(new OnClickListener() { 694 @Override 695 public void onClick(View v) { 696 sendMessage(msgItem, MSG_LIST_PLAY); 697 } 698 }); 699 mImageView.setOnLongClickListener(new OnLongClickListener() { 700 @Override 701 public boolean onLongClick(View v) { 702 return v.showContextMenu(); 703 } 704 }); 705 break; 706 707 default: 708 mImageView.setOnClickListener(null); 709 break; 710 } 711 } 712 713 private void drawRightStatusIndicator(MessageItem msgItem) { 714 // Locked icon 715 if (msgItem.mLocked) { 716 mLockedIndicator.setImageResource(R.drawable.ic_lock_message_sms); 717 mLockedIndicator.setVisibility(View.VISIBLE); 718 } else { 719 mLockedIndicator.setVisibility(View.GONE); 720 } 721 722 // Delivery icon - we can show a failed icon for both sms and mms, but for an actual 723 // delivery, we only show the icon for sms. We don't have the information here in mms to 724 // know whether the message has been delivered. For mms, msgItem.mDeliveryStatus set 725 // to MessageItem.DeliveryStatus.RECEIVED simply means the setting requesting a 726 // delivery report was turned on when the message was sent. Yes, it's confusing! 727 if ((msgItem.isOutgoingMessage() && msgItem.isFailedMessage()) || 728 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) { 729 mDeliveredIndicator.setImageResource(R.drawable.ic_list_alert_sms_failed); 730 mDeliveredIndicator.setVisibility(View.VISIBLE); 731 } else if (msgItem.isSms() && 732 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) { 733 mDeliveredIndicator.setImageResource(R.drawable.ic_sms_mms_delivered); 734 mDeliveredIndicator.setVisibility(View.VISIBLE); 735 } else { 736 mDeliveredIndicator.setVisibility(View.GONE); 737 } 738 739 // Message details icon - this icon is shown both for sms and mms messages. For mms, 740 // we show the icon if the read report or delivery report setting was set when the 741 // message was sent. Showing the icon tells the user there's more information 742 // by selecting the "View report" menu. 743 if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport 744 || (msgItem.isMms() && 745 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED)) { 746 mDetailsIndicator.setImageResource(R.drawable.ic_sms_mms_details); 747 mDetailsIndicator.setVisibility(View.VISIBLE); 748 } else { 749 mDetailsIndicator.setVisibility(View.GONE); 750 } 751 } 752 753 @Override 754 public void setImageRegionFit(String fit) { 755 // TODO Auto-generated method stub 756 } 757 758 @Override 759 public void setImageVisibility(boolean visible) { 760 // TODO Auto-generated method stub 761 } 762 763 @Override 764 public void setText(String name, String text) { 765 // TODO Auto-generated method stub 766 } 767 768 @Override 769 public void setTextVisibility(boolean visible) { 770 // TODO Auto-generated method stub 771 } 772 773 @Override 774 public void setVideo(String name, Uri uri) { 775 } 776 777 @Override 778 public void setVideoThumbnail(String name, Bitmap bitmap) { 779 showMmsView(true); 780 781 try { 782 mImageView.setImageBitmap(bitmap); 783 mImageView.setVisibility(VISIBLE); 784 } catch (java.lang.OutOfMemoryError e) { 785 Log.e(TAG, "setVideo: out of memory: ", e); 786 } 787 } 788 789 @Override 790 public void setVideoVisibility(boolean visible) { 791 // TODO Auto-generated method stub 792 } 793 794 @Override 795 public void stopAudio() { 796 // TODO Auto-generated method stub 797 } 798 799 @Override 800 public void stopVideo() { 801 // TODO Auto-generated method stub 802 } 803 804 @Override 805 public void reset() { 806 } 807 808 @Override 809 public void setVisibility(boolean visible) { 810 // TODO Auto-generated method stub 811 } 812 813 @Override 814 public void pauseAudio() { 815 // TODO Auto-generated method stub 816 817 } 818 819 @Override 820 public void pauseVideo() { 821 // TODO Auto-generated method stub 822 823 } 824 825 @Override 826 public void seekAudio(int seekTo) { 827 // TODO Auto-generated method stub 828 829 } 830 831 @Override 832 public void seekVideo(int seekTo) { 833 // TODO Auto-generated method stub 834 835 } 836 } 837