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