MessageListItem.java revision e753c800a1533ee7e4f8bc3843991269f1295ec0
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.Canvas;
30import android.graphics.Paint;
31import android.graphics.Paint.FontMetricsInt;
32import android.graphics.Path;
33import android.graphics.Typeface;
34import android.graphics.drawable.Drawable;
35import android.net.Uri;
36import android.os.Handler;
37import android.os.Message;
38import android.provider.ContactsContract.Profile;
39import android.provider.Telephony.Sms;
40import android.telephony.PhoneNumberUtils;
41import android.telephony.TelephonyManager;
42import android.text.Html;
43import android.text.SpannableStringBuilder;
44import android.text.TextUtils;
45import android.text.method.HideReturnsTransformationMethod;
46import android.text.style.ForegroundColorSpan;
47import android.text.style.LineHeightSpan;
48import android.text.style.StyleSpan;
49import android.text.style.TextAppearanceSpan;
50import android.text.style.URLSpan;
51import android.util.AttributeSet;
52import android.util.Log;
53import android.view.View;
54import android.view.View.OnClickListener;
55import android.view.ViewGroup;
56import android.widget.ArrayAdapter;
57import android.widget.Button;
58import android.widget.ImageButton;
59import android.widget.ImageView;
60import android.widget.LinearLayout;
61import android.widget.TextView;
62
63import com.android.mms.MmsApp;
64import com.android.mms.R;
65import com.android.mms.data.Contact;
66import com.android.mms.data.WorkingMessage;
67import com.android.mms.model.SlideModel;
68import com.android.mms.model.SlideshowModel;
69import com.android.mms.transaction.Transaction;
70import com.android.mms.transaction.TransactionBundle;
71import com.android.mms.transaction.TransactionService;
72import com.android.mms.util.DownloadManager;
73import com.android.mms.util.ItemLoadedCallback;
74import com.android.mms.util.SmileyParser;
75import com.android.mms.util.ThumbnailManager.ImageLoaded;
76import com.google.android.mms.ContentType;
77import com.google.android.mms.pdu.PduHeaders;
78
79/**
80 * This class provides view of a message in the messages list.
81 */
82public class MessageListItem extends LinearLayout implements
83        SlideViewInterface, OnClickListener {
84    public static final String EXTRA_URLS = "com.android.mms.ExtraUrls";
85
86    private static final String TAG = "MessageListItem";
87    private static final boolean DEBUG = false;
88    private static final boolean DEBUG_DONT_LOAD_IMAGES = false;
89
90    static final int MSG_LIST_EDIT    = 1;
91    static final int MSG_LIST_PLAY    = 2;
92    static final int MSG_LIST_DETAILS = 3;
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 mPathRight;
109    private Path mPathLeft;
110    private Paint mPaint;
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                        " mMessageItem.mAttachmentType: " + mMessageItem.mAttachmentType);
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                            mMessageItem.setCachedFormattedMessage(null);
369                            bindCommonMessage();
370                        }
371                    }
372                });
373            } else {
374                if (mPresenter == null) {
375                    mPresenter = PresenterFactory.getPresenter(
376                            "MmsThumbnailPresenter", mContext,
377                            this, mMessageItem.mSlideshow);
378                } else {
379                    mPresenter.setModel(mMessageItem.mSlideshow);
380                    mPresenter.setView(this);
381                }
382                if (mImageLoadedCallback == null) {
383                    mImageLoadedCallback = new ImageLoadedCallback(this);
384                } else {
385                    mImageLoadedCallback.reset(this);
386                }
387                mPresenter.present(mImageLoadedCallback);
388            }
389        }
390        drawRightStatusIndicator(mMessageItem);
391
392        requestLayout();
393    }
394
395    static private class ImageLoadedCallback implements ItemLoadedCallback<ImageLoaded> {
396        private long mMessageId;
397        private final MessageListItem mListItem;
398
399        public ImageLoadedCallback(MessageListItem listItem) {
400            mListItem = listItem;
401            mMessageId = listItem.getMessageItem().getMessageId();
402        }
403
404        public void reset(MessageListItem listItem) {
405            mMessageId = listItem.getMessageItem().getMessageId();
406        }
407
408        public void onItemLoaded(ImageLoaded imageLoaded, Throwable exception) {
409            if (DEBUG_DONT_LOAD_IMAGES) {
410                return;
411            }
412            // Make sure we're still pointing to the same message. The list item could have
413            // been recycled.
414            MessageItem msgItem = mListItem.mMessageItem;
415            if (msgItem != null && msgItem.getMessageId() == mMessageId) {
416                if (imageLoaded.mIsVideo) {
417                    mListItem.setVideoThumbnail(null, imageLoaded.mBitmap);
418                } else {
419                    mListItem.setImage(null, imageLoaded.mBitmap);
420                }
421            }
422        }
423    }
424
425    @Override
426    public void startAudio() {
427        // TODO Auto-generated method stub
428    }
429
430    @Override
431    public void startVideo() {
432        // TODO Auto-generated method stub
433    }
434
435    @Override
436    public void setAudio(Uri audio, String name, Map<String, ?> extras) {
437        // TODO Auto-generated method stub
438    }
439
440    @Override
441    public void setImage(String name, Bitmap bitmap) {
442        showMmsView(true);
443
444        try {
445            mImageView.setImageBitmap(bitmap);
446            mImageView.setVisibility(VISIBLE);
447        } catch (java.lang.OutOfMemoryError e) {
448            Log.e(TAG, "setImage: out of memory: ", e);
449        }
450    }
451
452    private void showMmsView(boolean visible) {
453        if (mMmsView == null) {
454            mMmsView = findViewById(R.id.mms_view);
455            // if mMmsView is still null here, that mean the mms section hasn't been inflated
456
457            if (visible && mMmsView == null) {
458                //inflate the mms view_stub
459                View mmsStub = findViewById(R.id.mms_layout_view_stub);
460                mmsStub.setVisibility(View.VISIBLE);
461                mMmsView = findViewById(R.id.mms_view);
462            }
463        }
464        if (mMmsView != null) {
465            if (mImageView == null) {
466                mImageView = (ImageView) findViewById(R.id.image_view);
467            }
468            if (mSlideShowButton == null) {
469                mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button);
470            }
471            mMmsView.setVisibility(visible ? View.VISIBLE : View.GONE);
472            mImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
473        }
474    }
475
476    private void inflateDownloadControls() {
477        if (mDownloadButton == null) {
478            //inflate the download controls
479            findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE);
480            mDownloadButton = (Button) findViewById(R.id.btn_download_msg);
481            mDownloadingLabel = (TextView) findViewById(R.id.label_downloading);
482        }
483    }
484
485
486    private LineHeightSpan mSpan = new LineHeightSpan() {
487        @Override
488        public void chooseHeight(CharSequence text, int start,
489                int end, int spanstartv, int v, FontMetricsInt fm) {
490            fm.ascent -= 10;
491        }
492    };
493
494    TextAppearanceSpan mTextSmallSpan =
495        new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Small);
496
497    ForegroundColorSpan mColorSpan = null;  // set in ctor
498
499    private CharSequence formatMessage(MessageItem msgItem, String contact, String body,
500                                       String subject, Pattern highlight,
501                                       String contentType) {
502        SpannableStringBuilder buf = new SpannableStringBuilder();
503
504        boolean hasSubject = !TextUtils.isEmpty(subject);
505        SmileyParser parser = SmileyParser.getInstance();
506        if (hasSubject) {
507            CharSequence smilizedSubject = parser.addSmileySpans(subject);
508            // Can't use the normal getString() with extra arguments for string replacement
509            // because it doesn't preserve the SpannableText returned by addSmileySpans.
510            // We have to manually replace the %s with our text.
511            buf.append(TextUtils.replace(mContext.getResources().getString(R.string.inline_subject),
512                    new String[] { "%s" }, new CharSequence[] { smilizedSubject }));
513        }
514
515        if (!TextUtils.isEmpty(body)) {
516            // Converts html to spannable if ContentType is "text/html".
517            if (contentType != null && ContentType.TEXT_HTML.equals(contentType)) {
518                buf.append("\n");
519                buf.append(Html.fromHtml(body));
520            } else {
521                if (hasSubject) {
522                    buf.append(" - ");
523                }
524                buf.append(parser.addSmileySpans(body));
525            }
526        }
527
528        if (highlight != null) {
529            Matcher m = highlight.matcher(buf.toString());
530            while (m.find()) {
531                buf.setSpan(new StyleSpan(Typeface.BOLD), m.start(), m.end(), 0);
532            }
533        }
534        return buf;
535    }
536
537    private void drawPlaybackButton(MessageItem msgItem) {
538        switch (msgItem.mAttachmentType) {
539            case WorkingMessage.SLIDESHOW:
540            case WorkingMessage.AUDIO:
541            case WorkingMessage.VIDEO:
542                // Show the 'Play' button and bind message info on it.
543                mSlideShowButton.setTag(msgItem);
544                // Set call-back for the 'Play' button.
545                mSlideShowButton.setOnClickListener(this);
546                mSlideShowButton.setVisibility(View.VISIBLE);
547                setLongClickable(true);
548
549                // When we show the mSlideShowButton, this list item's onItemClickListener doesn't
550                // get called. (It gets set in ComposeMessageActivity:
551                // mMsgListView.setOnItemClickListener) Here we explicitly set the item's
552                // onClickListener. It allows the item to respond to embedded html links and at the
553                // same time, allows the slide show play button to work.
554                setOnClickListener(new OnClickListener() {
555                    @Override
556                    public void onClick(View v) {
557                        onMessageListItemClick();
558                    }
559                });
560                break;
561            default:
562                mSlideShowButton.setVisibility(View.GONE);
563                break;
564        }
565    }
566
567    // OnClick Listener for the playback button
568    @Override
569    public void onClick(View v) {
570        sendMessage(mMessageItem, MSG_LIST_PLAY);
571    }
572
573    private void sendMessage(MessageItem messageItem, int message) {
574        if (mHandler != null) {
575            Message msg = Message.obtain(mHandler, message);
576            msg.obj = messageItem;
577            msg.sendToTarget(); // See ComposeMessageActivity.mMessageListItemHandler.handleMessage
578        }
579    }
580
581    public void onMessageListItemClick() {
582        // If the message is a failed one, clicking it should reload it in the compose view,
583        // regardless of whether it has links in it
584        if (mMessageItem != null &&
585                mMessageItem.isOutgoingMessage() &&
586                mMessageItem.isFailedMessage() ) {
587
588            // Assuming the current message is a failed one, reload it into the compose view so
589            // the user can resend it.
590            sendMessage(mMessageItem, MSG_LIST_EDIT);
591            return;
592        }
593
594        // Check for links. If none, do nothing; if 1, open it; if >1, ask user to pick one
595        final URLSpan[] spans = mBodyTextView.getUrls();
596
597        if (spans.length == 0) {
598            sendMessage(mMessageItem, MSG_LIST_DETAILS);    // show the message details dialog
599        } else if (spans.length == 1) {
600            spans[0].onClick(mBodyTextView);
601        } else {
602            ArrayAdapter<URLSpan> adapter =
603                new ArrayAdapter<URLSpan>(mContext, android.R.layout.select_dialog_item, spans) {
604                @Override
605                public View getView(int position, View convertView, ViewGroup parent) {
606                    View v = super.getView(position, convertView, parent);
607                    try {
608                        URLSpan span = getItem(position);
609                        String url = span.getURL();
610                        Uri uri = Uri.parse(url);
611                        TextView tv = (TextView) v;
612                        Drawable d = mContext.getPackageManager().getActivityIcon(
613                                new Intent(Intent.ACTION_VIEW, uri));
614                        if (d != null) {
615                            d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight());
616                            tv.setCompoundDrawablePadding(10);
617                            tv.setCompoundDrawables(d, null, null, null);
618                        }
619                        final String telPrefix = "tel:";
620                        if (url.startsWith(telPrefix)) {
621                            if ((mDefaultCountryIso == null) || mDefaultCountryIso.isEmpty()) {
622                                url = url.substring(telPrefix.length());
623                            }
624                            else {
625                                url = PhoneNumberUtils.formatNumber(
626                                        url.substring(telPrefix.length()), mDefaultCountryIso);
627                            }
628                        }
629                        tv.setText(url);
630                    } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
631                        // it's ok if we're unable to set the drawable for this view - the user
632                        // can still use it
633                    }
634                    return v;
635                }
636            };
637
638            AlertDialog.Builder b = new AlertDialog.Builder(mContext);
639
640            DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() {
641                @Override
642                public final void onClick(DialogInterface dialog, int which) {
643                    if (which >= 0) {
644                        spans[which].onClick(mBodyTextView);
645                    }
646                    dialog.dismiss();
647                }
648            };
649
650            b.setTitle(R.string.select_link_title);
651            b.setCancelable(true);
652            b.setAdapter(adapter, click);
653
654            b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
655                @Override
656                public final void onClick(DialogInterface dialog, int which) {
657                    dialog.dismiss();
658                }
659            });
660
661            b.show();
662        }
663    }
664
665    private void setOnClickListener(final MessageItem msgItem) {
666        switch(msgItem.mAttachmentType) {
667            case WorkingMessage.IMAGE:
668            case WorkingMessage.VIDEO:
669                mImageView.setOnClickListener(new OnClickListener() {
670                    @Override
671                    public void onClick(View v) {
672                        sendMessage(msgItem, MSG_LIST_PLAY);
673                    }
674                });
675                mImageView.setOnLongClickListener(new OnLongClickListener() {
676                    @Override
677                    public boolean onLongClick(View v) {
678                        return v.showContextMenu();
679                    }
680                });
681                break;
682
683            default:
684                mImageView.setOnClickListener(null);
685                break;
686            }
687    }
688
689    private void drawRightStatusIndicator(MessageItem msgItem) {
690        // Locked icon
691        if (msgItem.mLocked) {
692            mLockedIndicator.setImageResource(R.drawable.ic_lock_message_sms);
693            mLockedIndicator.setVisibility(View.VISIBLE);
694        } else {
695            mLockedIndicator.setVisibility(View.GONE);
696        }
697
698        // Delivery icon - we can show a failed icon for both sms and mms, but for an actual
699        // delivery, we only show the icon for sms. We don't have the information here in mms to
700        // know whether the message has been delivered. For mms, msgItem.mDeliveryStatus set
701        // to MessageItem.DeliveryStatus.RECEIVED simply means the setting requesting a
702        // delivery report was turned on when the message was sent. Yes, it's confusing!
703        if ((msgItem.isOutgoingMessage() && msgItem.isFailedMessage()) ||
704                msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) {
705            mDeliveredIndicator.setImageResource(R.drawable.ic_list_alert_sms_failed);
706            mDeliveredIndicator.setVisibility(View.VISIBLE);
707        } else if (msgItem.isSms() &&
708                msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) {
709            mDeliveredIndicator.setImageResource(R.drawable.ic_sms_mms_delivered);
710            mDeliveredIndicator.setVisibility(View.VISIBLE);
711        } else {
712            mDeliveredIndicator.setVisibility(View.GONE);
713        }
714
715        // Message details icon - this icon is shown both for sms and mms messages. For mms,
716        // we show the icon if the read report or delivery report setting was set when the
717        // message was sent. Showing the icon tells the user there's more information
718        // by selecting the "View report" menu.
719        if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport
720                || (msgItem.isMms() &&
721                        msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED)) {
722            mDetailsIndicator.setImageResource(R.drawable.ic_sms_mms_details);
723            mDetailsIndicator.setVisibility(View.VISIBLE);
724        } else {
725            mDetailsIndicator.setVisibility(View.GONE);
726        }
727    }
728
729    @Override
730    public void setImageRegionFit(String fit) {
731        // TODO Auto-generated method stub
732    }
733
734    @Override
735    public void setImageVisibility(boolean visible) {
736        // TODO Auto-generated method stub
737    }
738
739    @Override
740    public void setText(String name, String text) {
741        // TODO Auto-generated method stub
742    }
743
744    @Override
745    public void setTextVisibility(boolean visible) {
746        // TODO Auto-generated method stub
747    }
748
749    @Override
750    public void setVideo(String name, Uri uri) {
751    }
752
753    @Override
754    public void setVideoThumbnail(String name, Bitmap bitmap) {
755        showMmsView(true);
756
757        try {
758            mImageView.setImageBitmap(bitmap);
759            mImageView.setVisibility(VISIBLE);
760        } catch (java.lang.OutOfMemoryError e) {
761            Log.e(TAG, "setVideo: out of memory: ", e);
762        }
763    }
764
765    @Override
766    public void setVideoVisibility(boolean visible) {
767        // TODO Auto-generated method stub
768    }
769
770    @Override
771    public void stopAudio() {
772        // TODO Auto-generated method stub
773    }
774
775    @Override
776    public void stopVideo() {
777        // TODO Auto-generated method stub
778    }
779
780    @Override
781    public void reset() {
782    }
783
784    @Override
785    public void setVisibility(boolean visible) {
786        // TODO Auto-generated method stub
787    }
788
789    @Override
790    public void pauseAudio() {
791        // TODO Auto-generated method stub
792
793    }
794
795    @Override
796    public void pauseVideo() {
797        // TODO Auto-generated method stub
798
799    }
800
801    @Override
802    public void seekAudio(int seekTo) {
803        // TODO Auto-generated method stub
804
805    }
806
807    @Override
808    public void seekVideo(int seekTo) {
809        // TODO Auto-generated method stub
810
811    }
812
813    /**
814     * Override dispatchDraw so that we can put our own background and border in.
815     * This is all complexity to support a shared border from one item to the next.
816     */
817    @Override
818    public void dispatchDraw(Canvas c) {
819        super.dispatchDraw(c);
820
821        // This custom border is causing our scrolling fps to drop from 60+ to the mid 40's.
822        // Commenting out for now until we come up with a new UI design that doesn't require
823        // the border.
824        return;
825
826//        View v = mMessageBlock;
827//        if (v != null) {
828//            Path path = null;
829//            if (mAvatar.getPosition() == Divot.RIGHT_UPPER) {
830//                if (mPathRight == null) {
831//                    float r = v.getWidth() - 1;
832//                    float b = v.getHeight();
833//
834//                    mPathRight = new Path();
835//                    mPathRight.moveTo(0, mAvatar.getCloseOffset());
836//                    mPathRight.lineTo(0, 0);
837//                    mPathRight.lineTo(r, 0);
838//                    mPathRight.lineTo(r, b);
839//                    mPathRight.lineTo(0, b);
840//                    mPathRight.lineTo(0, mAvatar.getFarOffset());
841//                }
842//                path = mPathRight;
843//            } else if (mAvatar.getPosition() == Divot.LEFT_UPPER) {
844//                if (mPathLeft == null) {
845//                    float r = v.getWidth() - 1;
846//                    float b = v.getHeight();
847//
848//                    mPathLeft = new Path();
849//                    mPathLeft.moveTo(r, mAvatar.getCloseOffset());
850//                    mPathLeft.lineTo(r, 0);
851//                    mPathLeft.lineTo(0, 0);
852//                    mPathLeft.lineTo(0, b);
853//                    mPathLeft.lineTo(r, b);
854//                    mPathLeft.lineTo(r, mAvatar.getFarOffset());
855//                }
856//                path = mPathLeft;
857//            }
858//            if (mPaint == null) {
859//                mPaint = new Paint();
860//                mPaint.setColor(0xffcccccc);
861//                mPaint.setStrokeWidth(1F);
862//                mPaint.setStyle(Paint.Style.STROKE);
863//                mPaint.setColor(0xff00ff00);  // turn on for debugging, draws lines in green
864//            }
865//            c.translate(v.getX(), v.getY());
866//            c.drawPath(path, mPaint);
867//        }
868    }
869}
870