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