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