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.Path; 33 import android.graphics.Typeface; 34 import android.graphics.Paint.FontMetricsInt; 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.Browser; 40 import android.provider.ContactsContract.Profile; 41 import android.provider.Telephony.Sms; 42 import android.telephony.PhoneNumberUtils; 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.ViewGroup; 56 import android.view.View.OnClickListener; 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.MmsApp; 65 import com.android.mms.R; 66 import com.android.mms.data.Contact; 67 import com.android.mms.data.WorkingMessage; 68 import com.android.mms.transaction.Transaction; 69 import com.android.mms.transaction.TransactionBundle; 70 import com.android.mms.transaction.TransactionService; 71 import com.android.mms.util.DownloadManager; 72 import com.android.mms.util.SmileyParser; 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 StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD); 85 86 static final int MSG_LIST_EDIT_MMS = 1; 87 static final int MSG_LIST_EDIT_SMS = 2; 88 89 private View mMmsView; 90 private ImageView mImageView; 91 private ImageView mLockedIndicator; 92 private ImageView mDeliveredIndicator; 93 private ImageView mDetailsIndicator; 94 private ImageButton mSlideShowButton; 95 private TextView mBodyTextView; 96 private Button mDownloadButton; 97 private TextView mDownloadingLabel; 98 private Handler mHandler; 99 private MessageItem mMessageItem; 100 private String mDefaultCountryIso; 101 private TextView mDateView; 102 public View mMessageBlock; 103 private Path mPath = new Path(); 104 private Paint mPaint = new Paint(); 105 private QuickContactDivot mAvatar; 106 private boolean mIsLastItemInList; 107 static private Drawable sDefaultContactImage; 108 109 public MessageListItem(Context context) { 110 super(context); 111 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 112 113 if (sDefaultContactImage == null) { 114 sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture); 115 } 116 } 117 118 public MessageListItem(Context context, AttributeSet attrs) { 119 super(context, attrs); 120 121 int color = mContext.getResources().getColor(R.color.timestamp_color); 122 mColorSpan = new ForegroundColorSpan(color); 123 mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso(); 124 125 if (sDefaultContactImage == null) { 126 sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture); 127 } 128 } 129 130 @Override 131 protected void onFinishInflate() { 132 super.onFinishInflate(); 133 134 mBodyTextView = (TextView) findViewById(R.id.text_view); 135 mDateView = (TextView) findViewById(R.id.date_view); 136 mLockedIndicator = (ImageView) findViewById(R.id.locked_indicator); 137 mDeliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator); 138 mDetailsIndicator = (ImageView) findViewById(R.id.details_indicator); 139 mAvatar = (QuickContactDivot) findViewById(R.id.avatar); 140 mMessageBlock = findViewById(R.id.message_block); 141 } 142 143 public void bind(MessageItem msgItem, boolean isLastItem) { 144 mMessageItem = msgItem; 145 mIsLastItemInList = isLastItem; 146 147 setLongClickable(false); 148 setClickable(false); // let the list view handle clicks on the item normally. When 149 // clickable is true, clicks bypass the listview and go straight 150 // to this listitem. We always want the listview to handle the 151 // clicks first. 152 153 switch (msgItem.mMessageType) { 154 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 155 bindNotifInd(msgItem); 156 break; 157 default: 158 bindCommonMessage(msgItem); 159 break; 160 } 161 } 162 163 public void unbind() { 164 // Clear all references to the message item, which can contain attachments and other 165 // memory-intensive objects 166 mMessageItem = null; 167 if (mImageView != null) { 168 // Because #setOnClickListener may have set the listener to an object that has the 169 // message item in its closure. 170 mImageView.setOnClickListener(null); 171 } 172 if (mSlideShowButton != null) { 173 // Because #drawPlaybackButton sets the tag to mMessageItem 174 mSlideShowButton.setTag(null); 175 } 176 } 177 178 public MessageItem getMessageItem() { 179 return mMessageItem; 180 } 181 182 public void setMsgListItemHandler(Handler handler) { 183 mHandler = handler; 184 } 185 186 private void bindNotifInd(final MessageItem msgItem) { 187 hideMmsViewIfNeeded(); 188 189 String msgSizeText = mContext.getString(R.string.message_size_label) 190 + String.valueOf((msgItem.mMessageSize + 1023) / 1024) 191 + mContext.getString(R.string.kilobyte); 192 193 mBodyTextView.setText(formatMessage(msgItem, msgItem.mContact, null, msgItem.mSubject, 194 msgItem.mHighlight, msgItem.mTextContentType)); 195 196 mDateView.setText(msgSizeText + " " + msgItem.mTimestamp); 197 198 int state = DownloadManager.getInstance().getState(msgItem.mMessageUri); 199 switch (state) { 200 case DownloadManager.STATE_DOWNLOADING: 201 inflateDownloadControls(); 202 mDownloadingLabel.setVisibility(View.VISIBLE); 203 mDownloadButton.setVisibility(View.GONE); 204 break; 205 case DownloadManager.STATE_UNSTARTED: 206 case DownloadManager.STATE_TRANSIENT_FAILURE: 207 case DownloadManager.STATE_PERMANENT_FAILURE: 208 default: 209 setLongClickable(true); 210 inflateDownloadControls(); 211 mDownloadingLabel.setVisibility(View.GONE); 212 mDownloadButton.setVisibility(View.VISIBLE); 213 mDownloadButton.setOnClickListener(new OnClickListener() { 214 @Override 215 public void onClick(View v) { 216 mDownloadingLabel.setVisibility(View.VISIBLE); 217 mDownloadButton.setVisibility(View.GONE); 218 Intent intent = new Intent(mContext, TransactionService.class); 219 intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString()); 220 intent.putExtra(TransactionBundle.TRANSACTION_TYPE, 221 Transaction.RETRIEVE_TRANSACTION); 222 mContext.startService(intent); 223 } 224 }); 225 break; 226 } 227 228 // Hide the indicators. 229 mLockedIndicator.setVisibility(View.GONE); 230 mDeliveredIndicator.setVisibility(View.GONE); 231 mDetailsIndicator.setVisibility(View.GONE); 232 updateAvatarView(msgItem.mAddress, false); 233 } 234 235 private void updateAvatarView(String addr, boolean isSelf) { 236 Drawable avatarDrawable; 237 if (isSelf || !TextUtils.isEmpty(addr)) { 238 Contact contact = isSelf ? Contact.getMe(false) : Contact.get(addr, false); 239 avatarDrawable = contact.getAvatar(mContext, sDefaultContactImage); 240 241 if (isSelf) { 242 mAvatar.assignContactUri(Profile.CONTENT_URI); 243 } else { 244 if (contact.existsInDatabase()) { 245 mAvatar.assignContactUri(contact.getUri()); 246 } else { 247 mAvatar.assignContactFromPhone(contact.getNumber(), true); 248 } 249 } 250 } else { 251 avatarDrawable = sDefaultContactImage; 252 } 253 mAvatar.setImageDrawable(avatarDrawable); 254 } 255 256 private void bindCommonMessage(final MessageItem msgItem) { 257 if (mDownloadButton != null) { 258 mDownloadButton.setVisibility(View.GONE); 259 mDownloadingLabel.setVisibility(View.GONE); 260 } 261 // Since the message text should be concatenated with the sender's 262 // address(or name), I have to display it here instead of 263 // displaying it by the Presenter. 264 mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 265 266 boolean isSelf = Sms.isOutgoingFolder(msgItem.mBoxId); 267 String addr = isSelf ? null : msgItem.mAddress; 268 updateAvatarView(addr, isSelf); 269 270 // Get and/or lazily set the formatted message from/on the 271 // MessageItem. Because the MessageItem instances come from a 272 // cache (currently of size ~50), the hit rate on avoiding the 273 // expensive formatMessage() call is very high. 274 CharSequence formattedMessage = msgItem.getCachedFormattedMessage(); 275 if (formattedMessage == null) { 276 formattedMessage = formatMessage(msgItem, msgItem.mContact, msgItem.mBody, 277 msgItem.mSubject, 278 msgItem.mHighlight, msgItem.mTextContentType); 279 } 280 mBodyTextView.setText(formattedMessage); 281 282 // If we're in the process of sending a message (i.e. pending), then we show a "SENDING..." 283 // string in place of the timestamp. 284 mDateView.setText(msgItem.isSending() ? 285 mContext.getResources().getString(R.string.sending_message) : 286 msgItem.mTimestamp); 287 288 if (msgItem.isSms()) { 289 hideMmsViewIfNeeded(); 290 } else { 291 Presenter presenter = PresenterFactory.getPresenter( 292 "MmsThumbnailPresenter", mContext, 293 this, msgItem.mSlideshow); 294 presenter.present(); 295 296 if (msgItem.mAttachmentType != WorkingMessage.TEXT) { 297 inflateMmsView(); 298 mMmsView.setVisibility(View.VISIBLE); 299 setOnClickListener(msgItem); 300 drawPlaybackButton(msgItem); 301 } else { 302 hideMmsViewIfNeeded(); 303 } 304 } 305 drawRightStatusIndicator(msgItem); 306 307 requestLayout(); 308 } 309 310 private void hideMmsViewIfNeeded() { 311 if (mMmsView != null) { 312 mMmsView.setVisibility(View.GONE); 313 } 314 } 315 316 @Override 317 public void startAudio() { 318 // TODO Auto-generated method stub 319 } 320 321 @Override 322 public void startVideo() { 323 // TODO Auto-generated method stub 324 } 325 326 @Override 327 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 328 // TODO Auto-generated method stub 329 } 330 331 @Override 332 public void setImage(String name, Bitmap bitmap) { 333 inflateMmsView(); 334 335 try { 336 if (null == bitmap) { 337 bitmap = BitmapFactory.decodeResource(getResources(), 338 R.drawable.ic_missing_thumbnail_picture); 339 } 340 mImageView.setImageBitmap(bitmap); 341 mImageView.setVisibility(VISIBLE); 342 } catch (java.lang.OutOfMemoryError e) { 343 Log.e(TAG, "setImage: out of memory: ", e); 344 } 345 } 346 347 private void inflateMmsView() { 348 if (mMmsView == null) { 349 //inflate the surrounding view_stub 350 findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE); 351 352 mMmsView = findViewById(R.id.mms_view); 353 mImageView = (ImageView) findViewById(R.id.image_view); 354 mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); 355 } 356 } 357 358 private void inflateDownloadControls() { 359 if (mDownloadButton == null) { 360 //inflate the download controls 361 findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); 362 mDownloadButton = (Button) findViewById(R.id.btn_download_msg); 363 mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); 364 } 365 } 366 367 368 private LineHeightSpan mSpan = new LineHeightSpan() { 369 @Override 370 public void chooseHeight(CharSequence text, int start, 371 int end, int spanstartv, int v, FontMetricsInt fm) { 372 fm.ascent -= 10; 373 } 374 }; 375 376 TextAppearanceSpan mTextSmallSpan = 377 new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Small); 378 379 ForegroundColorSpan mColorSpan = null; // set in ctor 380 381 private CharSequence formatMessage(MessageItem msgItem, String contact, String body, 382 String subject, Pattern highlight, 383 String contentType) { 384 SpannableStringBuilder buf = new SpannableStringBuilder(); 385 386 boolean hasSubject = !TextUtils.isEmpty(subject); 387 SmileyParser parser = SmileyParser.getInstance(); 388 if (hasSubject) { 389 CharSequence smilizedSubject = parser.addSmileySpans(subject); 390 // Can't use the normal getString() with extra arguments for string replacement 391 // because it doesn't preserve the SpannableText returned by addSmileySpans. 392 // We have to manually replace the %s with our text. 393 buf.append(TextUtils.replace(mContext.getResources().getString(R.string.inline_subject), 394 new String[] { "%s" }, new CharSequence[] { smilizedSubject })); 395 } 396 397 if (!TextUtils.isEmpty(body)) { 398 // Converts html to spannable if ContentType is "text/html". 399 if (contentType != null && ContentType.TEXT_HTML.equals(contentType)) { 400 buf.append("\n"); 401 buf.append(Html.fromHtml(body)); 402 } else { 403 if (hasSubject) { 404 buf.append(" - "); 405 } 406 buf.append(parser.addSmileySpans(body)); 407 } 408 } 409 410 if (highlight != null) { 411 Matcher m = highlight.matcher(buf.toString()); 412 while (m.find()) { 413 buf.setSpan(new StyleSpan(Typeface.BOLD), m.start(), m.end(), 0); 414 } 415 } 416 return buf; 417 } 418 419 private void drawPlaybackButton(MessageItem msgItem) { 420 switch (msgItem.mAttachmentType) { 421 case WorkingMessage.SLIDESHOW: 422 case WorkingMessage.AUDIO: 423 case WorkingMessage.VIDEO: 424 // Show the 'Play' button and bind message info on it. 425 mSlideShowButton.setTag(msgItem); 426 // Set call-back for the 'Play' button. 427 mSlideShowButton.setOnClickListener(this); 428 mSlideShowButton.setVisibility(View.VISIBLE); 429 setLongClickable(true); 430 431 // When we show the mSlideShowButton, this list item's onItemClickListener doesn't 432 // get called. (It gets set in ComposeMessageActivity: 433 // mMsgListView.setOnItemClickListener) Here we explicitly set the item's 434 // onClickListener. It allows the item to respond to embedded html links and at the 435 // same time, allows the slide show play button to work. 436 setOnClickListener(new OnClickListener() { 437 @Override 438 public void onClick(View v) { 439 onMessageListItemClick(); 440 } 441 }); 442 break; 443 default: 444 mSlideShowButton.setVisibility(View.GONE); 445 break; 446 } 447 } 448 449 // OnClick Listener for the playback button 450 @Override 451 public void onClick(View v) { 452 MessageItem mi = (MessageItem) v.getTag(); 453 switch (mi.mAttachmentType) { 454 case WorkingMessage.VIDEO: 455 case WorkingMessage.AUDIO: 456 case WorkingMessage.SLIDESHOW: 457 MessageUtils.viewMmsMessageAttachment(mContext, mi.mMessageUri, mi.mSlideshow); 458 break; 459 } 460 } 461 462 public void onMessageListItemClick() { 463 // If the message is a failed one, clicking it should reload it in the compose view, 464 // regardless of whether it has links in it 465 if (mMessageItem != null && 466 mMessageItem.isOutgoingMessage() && 467 mMessageItem.isFailedMessage() ) { 468 recomposeFailedMessage(); 469 return; 470 } 471 472 // Check for links. If none, do nothing; if 1, open it; if >1, ask user to pick one 473 URLSpan[] spans = mBodyTextView.getUrls(); 474 475 if (spans.length == 0) { 476 // Do nothing. 477 } else if (spans.length == 1) { 478 Uri uri = Uri.parse(spans[0].getURL()); 479 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 480 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); 481 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 482 mContext.startActivity(intent); 483 } else { 484 final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); 485 486 ArrayAdapter<String> adapter = 487 new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) { 488 @Override 489 public View getView(int position, View convertView, ViewGroup parent) { 490 View v = super.getView(position, convertView, parent); 491 try { 492 String url = getItem(position).toString(); 493 TextView tv = (TextView) v; 494 Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 495 if (d != null) { 496 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 497 tv.setCompoundDrawablePadding(10); 498 tv.setCompoundDrawables(d, null, null, null); 499 } 500 final String telPrefix = "tel:"; 501 if (url.startsWith(telPrefix)) { 502 url = PhoneNumberUtils.formatNumber( 503 url.substring(telPrefix.length()), mDefaultCountryIso); 504 } 505 tv.setText(url); 506 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 507 // it's ok if we're unable to set the drawable for this view - the user 508 // can still use it 509 } 510 return v; 511 } 512 }; 513 514 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 515 516 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 517 @Override 518 public final void onClick(DialogInterface dialog, int which) { 519 if (which >= 0) { 520 Uri uri = Uri.parse(urls.get(which)); 521 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 522 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); 523 intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); 524 mContext.startActivity(intent); 525 } 526 dialog.dismiss(); 527 } 528 }; 529 530 b.setTitle(R.string.select_link_title); 531 b.setCancelable(true); 532 b.setAdapter(adapter, click); 533 534 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 535 @Override 536 public final void onClick(DialogInterface dialog, int which) { 537 dialog.dismiss(); 538 } 539 }); 540 541 b.show(); 542 } 543 } 544 545 private void setOnClickListener(final MessageItem msgItem) { 546 switch(msgItem.mAttachmentType) { 547 case WorkingMessage.IMAGE: 548 case WorkingMessage.VIDEO: 549 mImageView.setOnClickListener(new OnClickListener() { 550 @Override 551 public void onClick(View v) { 552 MessageUtils.viewMmsMessageAttachment(mContext, null, msgItem.mSlideshow); 553 } 554 }); 555 mImageView.setOnLongClickListener(new OnLongClickListener() { 556 @Override 557 public boolean onLongClick(View v) { 558 return v.showContextMenu(); 559 } 560 }); 561 break; 562 563 default: 564 mImageView.setOnClickListener(null); 565 break; 566 } 567 } 568 569 /** 570 * Assuming the current message is a failed one, reload it into the compose view so that the 571 * user can resend it. 572 */ 573 private void recomposeFailedMessage() { 574 String type = mMessageItem.mType; 575 final int what; 576 if (type.equals("sms")) { 577 what = MSG_LIST_EDIT_SMS; 578 } else { 579 what = MSG_LIST_EDIT_MMS; 580 } 581 if (null != mHandler) { 582 Message msg = Message.obtain(mHandler, what); 583 msg.obj = new Long(mMessageItem.mMsgId); 584 msg.sendToTarget(); 585 } 586 } 587 588 private void drawRightStatusIndicator(MessageItem msgItem) { 589 // Locked icon 590 if (msgItem.mLocked) { 591 mLockedIndicator.setImageResource(R.drawable.ic_lock_message_sms); 592 mLockedIndicator.setVisibility(View.VISIBLE); 593 } else { 594 mLockedIndicator.setVisibility(View.GONE); 595 } 596 597 // Delivery icon - we can show a failed icon for both sms and mms, but for an actual 598 // delivery, we only show the icon for sms. We don't have the information here in mms to 599 // know whether the message has been delivered. For mms, msgItem.mDeliveryStatus set 600 // to MessageItem.DeliveryStatus.RECEIVED simply means the setting requesting a 601 // delivery report was turned on when the message was sent. Yes, it's confusing! 602 if ((msgItem.isOutgoingMessage() && msgItem.isFailedMessage()) || 603 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) { 604 mDeliveredIndicator.setImageResource(R.drawable.ic_list_alert_sms_failed); 605 mDeliveredIndicator.setVisibility(View.VISIBLE); 606 } else if (msgItem.isSms() && 607 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) { 608 mDeliveredIndicator.setImageResource(R.drawable.ic_sms_mms_delivered); 609 mDeliveredIndicator.setVisibility(View.VISIBLE); 610 } else { 611 mDeliveredIndicator.setVisibility(View.GONE); 612 } 613 614 // Message details icon - this icon is shown both for sms and mms messages. For mms, 615 // we show the icon if the read report or delivery report setting was set when the 616 // message was sent. Showing the icon tells the user there's more information 617 // by selecting the "View report" menu. 618 if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport 619 || (msgItem.isMms() && 620 msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED)) { 621 mDetailsIndicator.setImageResource(R.drawable.ic_sms_mms_details); 622 mDetailsIndicator.setVisibility(View.VISIBLE); 623 } else { 624 mDetailsIndicator.setVisibility(View.GONE); 625 } 626 } 627 628 @Override 629 public void setImageRegionFit(String fit) { 630 // TODO Auto-generated method stub 631 } 632 633 @Override 634 public void setImageVisibility(boolean visible) { 635 // TODO Auto-generated method stub 636 } 637 638 @Override 639 public void setText(String name, String text) { 640 // TODO Auto-generated method stub 641 } 642 643 @Override 644 public void setTextVisibility(boolean visible) { 645 // TODO Auto-generated method stub 646 } 647 648 @Override 649 public void setVideo(String name, Uri video) { 650 inflateMmsView(); 651 652 try { 653 Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video); 654 if (null == bitmap) { 655 bitmap = BitmapFactory.decodeResource(getResources(), 656 R.drawable.ic_missing_thumbnail_video); 657 } 658 mImageView.setImageBitmap(bitmap); 659 mImageView.setVisibility(VISIBLE); 660 } catch (java.lang.OutOfMemoryError e) { 661 Log.e(TAG, "setVideo: out of memory: ", e); 662 } 663 } 664 665 @Override 666 public void setVideoVisibility(boolean visible) { 667 // TODO Auto-generated method stub 668 } 669 670 @Override 671 public void stopAudio() { 672 // TODO Auto-generated method stub 673 } 674 675 @Override 676 public void stopVideo() { 677 // TODO Auto-generated method stub 678 } 679 680 @Override 681 public void reset() { 682 if (mImageView != null) { 683 mImageView.setVisibility(GONE); 684 } 685 } 686 687 @Override 688 public void setVisibility(boolean visible) { 689 // TODO Auto-generated method stub 690 } 691 692 @Override 693 public void pauseAudio() { 694 // TODO Auto-generated method stub 695 696 } 697 698 @Override 699 public void pauseVideo() { 700 // TODO Auto-generated method stub 701 702 } 703 704 @Override 705 public void seekAudio(int seekTo) { 706 // TODO Auto-generated method stub 707 708 } 709 710 @Override 711 public void seekVideo(int seekTo) { 712 // TODO Auto-generated method stub 713 714 } 715 716 /** 717 * Override dispatchDraw so that we can put our own background and border in. 718 * This is all complexity to support a shared border from one item to the next. 719 */ 720 @Override 721 public void dispatchDraw(Canvas c) { 722 View v = mMessageBlock; 723 if (v != null) { 724 float l = v.getX(); 725 float t = v.getY(); 726 float r = v.getX() + v.getWidth(); 727 float b = v.getY() + v.getHeight(); 728 729 Path path = mPath; 730 path.reset(); 731 732 super.dispatchDraw(c); 733 734 path.reset(); 735 736 r -= 1; 737 738 // This block of code draws the border around the "message block" section 739 // of the layout. This would normally be a simple rectangle but we omit 740 // the border at the point of the avatar's divot. Also, the bottom is drawn 741 // 1 pixel below our own bounds to get it to line up with the border of 742 // the next item. 743 // 744 // But for the last item we draw the bottom in our own bounds -- so it will 745 // show up. 746 if (mIsLastItemInList) { 747 b -= 1; 748 } 749 if (mAvatar.getPosition() == Divot.RIGHT_UPPER) { 750 path.moveTo(l, t + mAvatar.getCloseOffset()); 751 path.lineTo(l, t); 752 path.lineTo(r, t); 753 path.lineTo(r, b); 754 path.lineTo(l, b); 755 path.lineTo(l, t + mAvatar.getFarOffset()); 756 } else if (mAvatar.getPosition() == Divot.LEFT_UPPER) { 757 path.moveTo(r, t + mAvatar.getCloseOffset()); 758 path.lineTo(r, t); 759 path.lineTo(l, t); 760 path.lineTo(l, b); 761 path.lineTo(r, b); 762 path.lineTo(r, t + mAvatar.getFarOffset()); 763 } 764 765 Paint paint = mPaint; 766 // paint.setColor(0xff00ff00); 767 paint.setColor(0xffcccccc); 768 paint.setStrokeWidth(1F); 769 paint.setStyle(Paint.Style.STROKE); 770 c.drawPath(path, paint); 771 } else { 772 super.dispatchDraw(c); 773 } 774 } 775 } 776