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