Home | History | Annotate | Download | only in ui
      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