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