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