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