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