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