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