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