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