MessageListItem.java revision ac5b230500508d676267f264dc4ce63ae0900937
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.Paint.FontMetricsInt;
30import android.graphics.Typeface;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.os.Handler;
34import android.os.Message;
35import android.provider.ContactsContract.Profile;
36import android.provider.Telephony.Sms;
37import android.telephony.PhoneNumberUtils;
38import android.telephony.TelephonyManager;
39import android.text.Html;
40import android.text.SpannableStringBuilder;
41import android.text.TextUtils;
42import android.text.method.HideReturnsTransformationMethod;
43import android.text.style.ForegroundColorSpan;
44import android.text.style.LineHeightSpan;
45import android.text.style.StyleSpan;
46import android.text.style.TextAppearanceSpan;
47import android.text.style.URLSpan;
48import android.util.AttributeSet;
49import android.util.Log;
50import android.view.View;
51import android.view.View.OnClickListener;
52import android.view.ViewGroup;
53import android.widget.ArrayAdapter;
54import android.widget.Button;
55import android.widget.ImageButton;
56import android.widget.ImageView;
57import android.widget.LinearLayout;
58import android.widget.TextView;
59
60import com.android.mms.MmsApp;
61import com.android.mms.R;
62import com.android.mms.data.Contact;
63import com.android.mms.data.WorkingMessage;
64import com.android.mms.model.SlideModel;
65import com.android.mms.model.SlideshowModel;
66import com.android.mms.transaction.Transaction;
67import com.android.mms.transaction.TransactionBundle;
68import com.android.mms.transaction.TransactionService;
69import com.android.mms.util.DownloadManager;
70import com.android.mms.util.ItemLoadedCallback;
71import com.android.mms.util.SmileyParser;
72import com.android.mms.util.ThumbnailManager.ImageLoaded;
73import com.google.android.mms.ContentType;
74import com.google.android.mms.pdu.PduHeaders;
75
76/**
77 * This class provides view of a message in the messages list.
78 */
79public class MessageListItem extends LinearLayout implements
80        SlideViewInterface, OnClickListener {
81    public static final String EXTRA_URLS = "com.android.mms.ExtraUrls";
82
83    private static final String TAG = "MessageListItem";
84    private static final boolean DEBUG = false;
85    private static final boolean DEBUG_DONT_LOAD_IMAGES = false;
86
87    static final int MSG_LIST_EDIT    = 1;
88    static final int MSG_LIST_PLAY    = 2;
89    static final int MSG_LIST_DETAILS = 3;
90
91    private View mMmsView;
92    private ImageView mImageView;
93    private ImageView mLockedIndicator;
94    private ImageView mDeliveredIndicator;
95    private ImageView mDetailsIndicator;
96    private ImageButton mSlideShowButton;
97    private TextView mBodyTextView;
98    private Button mDownloadButton;
99    private TextView mDownloadingLabel;
100    private Handler mHandler;
101    private MessageItem mMessageItem;
102    private String mDefaultCountryIso;
103    private TextView mDateView;
104    public View mMessageBlock;
105    private QuickContactDivot mAvatar;
106    static private Drawable sDefaultContactImage;
107    private Presenter mPresenter;
108    private int mPosition;      // for debugging
109    private ImageLoadedCallback mImageLoadedCallback;
110    private boolean mMultiRecipients;
111
112    public MessageListItem(Context context) {
113        super(context);
114        mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso();
115
116        if (sDefaultContactImage == null) {
117            sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture);
118        }
119    }
120
121    public MessageListItem(Context context, AttributeSet attrs) {
122        super(context, attrs);
123
124        int color = mContext.getResources().getColor(R.color.timestamp_color);
125        mColorSpan = new ForegroundColorSpan(color);
126        mDefaultCountryIso = MmsApp.getApplication().getCurrentCountryIso();
127
128        if (sDefaultContactImage == null) {
129            sDefaultContactImage = context.getResources().getDrawable(R.drawable.ic_contact_picture);
130        }
131    }
132
133    @Override
134    protected void onFinishInflate() {
135        super.onFinishInflate();
136
137        mBodyTextView = (TextView) findViewById(R.id.text_view);
138        mDateView = (TextView) findViewById(R.id.date_view);
139        mLockedIndicator = (ImageView) findViewById(R.id.locked_indicator);
140        mDeliveredIndicator = (ImageView) findViewById(R.id.delivered_indicator);
141        mDetailsIndicator = (ImageView) findViewById(R.id.details_indicator);
142        mAvatar = (QuickContactDivot) findViewById(R.id.avatar);
143        mMessageBlock = findViewById(R.id.message_block);
144    }
145
146    public void bind(MessageItem msgItem, boolean convHasMultiRecipients, int position) {
147        if (DEBUG) {
148            Log.v(TAG, "bind for item: " + position + " old: " +
149                   (mMessageItem != null ? mMessageItem.toString() : "NULL" ) +
150                    " new " + msgItem.toString());
151        }
152        boolean sameItem = mMessageItem != null && mMessageItem.mMsgId == msgItem.mMsgId;
153        mMessageItem = msgItem;
154
155        mPosition = position;
156        mMultiRecipients = convHasMultiRecipients;
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(sameItem);
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        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, null,
208                                            mMessageItem.mSubject,
209                                            mMessageItem.mHighlight,
210                                            mMessageItem.mTextContentType));
211
212        mDateView.setText(buildTimestampLine(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 String buildTimestampLine(String timestamp) {
262        if (!mMultiRecipients || mMessageItem.isMe() || TextUtils.isEmpty(mMessageItem.mContact)) {
263            // Never show "Me" for messages I sent.
264            return timestamp;
265        }
266        // This is a group conversation, show the sender's name on the same line as the timestamp.
267        return mContext.getString(R.string.message_timestamp_format, mMessageItem.mContact,
268                timestamp);
269    }
270
271    private void showDownloadingAttachment() {
272        inflateDownloadControls();
273        mDownloadingLabel.setVisibility(View.VISIBLE);
274        mDownloadButton.setVisibility(View.GONE);
275    }
276
277    private void updateAvatarView(String addr, boolean isSelf) {
278        Drawable avatarDrawable;
279        if (isSelf || !TextUtils.isEmpty(addr)) {
280            Contact contact = isSelf ? Contact.getMe(false) : Contact.get(addr, false);
281            avatarDrawable = contact.getAvatar(mContext, sDefaultContactImage);
282
283            if (isSelf) {
284                mAvatar.assignContactUri(Profile.CONTENT_URI);
285            } else {
286                if (contact.existsInDatabase()) {
287                    mAvatar.assignContactUri(contact.getUri());
288                } else {
289                    mAvatar.assignContactFromPhone(contact.getNumber(), true);
290                }
291            }
292        } else {
293            avatarDrawable = sDefaultContactImage;
294        }
295        mAvatar.setImageDrawable(avatarDrawable);
296    }
297
298    private void bindCommonMessage(final boolean sameItem) {
299        if (mDownloadButton != null) {
300            mDownloadButton.setVisibility(View.GONE);
301            mDownloadingLabel.setVisibility(View.GONE);
302        }
303        // Since the message text should be concatenated with the sender's
304        // address(or name), I have to display it here instead of
305        // displaying it by the Presenter.
306        mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
307
308        boolean haveLoadedPdu = mMessageItem.isSms() || mMessageItem.mSlideshow != null;
309        // Here we're avoiding reseting the avatar to the empty avatar when we're rebinding
310        // to the same item. This happens when there's a DB change which causes the message item
311        // cache in the MessageListAdapter to get cleared. When an mms MessageItem is newly
312        // created, it has no info in it except the message id. The info is eventually loaded
313        // and bindCommonMessage is called again (see onPduLoaded below). When we haven't loaded
314        // the pdu, we don't want to call updateAvatarView because it
315        // will set the avatar to the generic avatar then when this method is called again
316        // from onPduLoaded, it will reset to the real avatar. This test is to avoid that flash.
317        if (!sameItem || haveLoadedPdu) {
318            boolean isSelf = Sms.isOutgoingFolder(mMessageItem.mBoxId);
319            String addr = isSelf ? null : mMessageItem.mAddress;
320            updateAvatarView(addr, isSelf);
321        }
322
323        // Get and/or lazily set the formatted message from/on the
324        // MessageItem.  Because the MessageItem instances come from a
325        // cache (currently of size ~50), the hit rate on avoiding the
326        // expensive formatMessage() call is very high.
327        CharSequence formattedMessage = mMessageItem.getCachedFormattedMessage();
328        if (formattedMessage == null) {
329            formattedMessage = formatMessage(mMessageItem,
330                                             mMessageItem.mBody,
331                                             mMessageItem.mSubject,
332                                             mMessageItem.mHighlight,
333                                             mMessageItem.mTextContentType);
334            mMessageItem.setCachedFormattedMessage(formattedMessage);
335        }
336        if (!sameItem || haveLoadedPdu) {
337            mBodyTextView.setText(formattedMessage);
338        }
339
340        // Debugging code to put the URI of the image attachment in the body of the list item.
341        if (DEBUG) {
342            String debugText = null;
343            if (mMessageItem.mSlideshow == null) {
344                debugText = "NULL slideshow";
345            } else {
346                SlideModel slide = ((SlideshowModel) mMessageItem.mSlideshow).get(0);
347                if (slide == null) {
348                    debugText = "NULL first slide";
349                } else if (!slide.hasImage()) {
350                    debugText = "Not an image";
351                } else {
352                    debugText = slide.getImage().getUri().toString();
353                }
354            }
355            mBodyTextView.setText(mPosition + ": " + debugText);
356        }
357
358        // If we're in the process of sending a message (i.e. pending), then we show a "SENDING..."
359        // string in place of the timestamp.
360        if (!sameItem || haveLoadedPdu) {
361            mDateView.setText(buildTimestampLine(mMessageItem.isSending() ?
362                    mContext.getResources().getString(R.string.sending_message) :
363                        mMessageItem.mTimestamp));
364        }
365        if (mMessageItem.isSms()) {
366            showMmsView(false);
367            mMessageItem.setOnPduLoaded(null);
368        } else {
369            if (DEBUG) {
370                Log.v(TAG, "bindCommonMessage for item: " + mPosition + " " +
371                        mMessageItem.toString() +
372                        " mMessageItem.mAttachmentType: " + mMessageItem.mAttachmentType +
373                        " sameItem: " + sameItem);
374            }
375            if (mMessageItem.mAttachmentType != WorkingMessage.TEXT) {
376                if (!sameItem) {
377                    setImage(null, null);
378                }
379                setOnClickListener(mMessageItem);
380                drawPlaybackButton(mMessageItem);
381            } else {
382                showMmsView(false);
383            }
384            if (mMessageItem.mSlideshow == null) {
385                mMessageItem.setOnPduLoaded(new MessageItem.PduLoadedCallback() {
386                    public void onPduLoaded(MessageItem messageItem) {
387                        if (DEBUG) {
388                            Log.v(TAG, "PduLoadedCallback in MessageListItem for item: " + mPosition +
389                                    " " + (mMessageItem == null ? "NULL" : mMessageItem.toString()) +
390                                    " passed in item: " +
391                                    (messageItem == null ? "NULL" : messageItem.toString()));
392                        }
393                        if (messageItem != null && mMessageItem != null &&
394                                messageItem.getMessageId() == mMessageItem.getMessageId()) {
395                            mMessageItem.setCachedFormattedMessage(null);
396                            bindCommonMessage(true);
397                        }
398                    }
399                });
400            } else {
401                if (mPresenter == null) {
402                    mPresenter = PresenterFactory.getPresenter(
403                            "MmsThumbnailPresenter", mContext,
404                            this, mMessageItem.mSlideshow);
405                } else {
406                    mPresenter.setModel(mMessageItem.mSlideshow);
407                    mPresenter.setView(this);
408                }
409                if (mImageLoadedCallback == null) {
410                    mImageLoadedCallback = new ImageLoadedCallback(this);
411                } else {
412                    mImageLoadedCallback.reset(this);
413                }
414                mPresenter.present(mImageLoadedCallback);
415            }
416        }
417        drawRightStatusIndicator(mMessageItem);
418
419        requestLayout();
420    }
421
422    static private class ImageLoadedCallback implements ItemLoadedCallback<ImageLoaded> {
423        private long mMessageId;
424        private final MessageListItem mListItem;
425
426        public ImageLoadedCallback(MessageListItem listItem) {
427            mListItem = listItem;
428            mMessageId = listItem.getMessageItem().getMessageId();
429        }
430
431        public void reset(MessageListItem listItem) {
432            mMessageId = listItem.getMessageItem().getMessageId();
433        }
434
435        public void onItemLoaded(ImageLoaded imageLoaded, Throwable exception) {
436            if (DEBUG_DONT_LOAD_IMAGES) {
437                return;
438            }
439            // Make sure we're still pointing to the same message. The list item could have
440            // been recycled.
441            MessageItem msgItem = mListItem.mMessageItem;
442            if (msgItem != null && msgItem.getMessageId() == mMessageId) {
443                if (imageLoaded.mIsVideo) {
444                    mListItem.setVideoThumbnail(null, imageLoaded.mBitmap);
445                } else {
446                    mListItem.setImage(null, imageLoaded.mBitmap);
447                }
448            }
449        }
450    }
451
452    @Override
453    public void startAudio() {
454        // TODO Auto-generated method stub
455    }
456
457    @Override
458    public void startVideo() {
459        // TODO Auto-generated method stub
460    }
461
462    @Override
463    public void setAudio(Uri audio, String name, Map<String, ?> extras) {
464        // TODO Auto-generated method stub
465    }
466
467    @Override
468    public void setImage(String name, Bitmap bitmap) {
469        showMmsView(true);
470
471        try {
472            mImageView.setImageBitmap(bitmap);
473            mImageView.setVisibility(VISIBLE);
474        } catch (java.lang.OutOfMemoryError e) {
475            Log.e(TAG, "setImage: out of memory: ", e);
476        }
477    }
478
479    private void showMmsView(boolean visible) {
480        if (mMmsView == null) {
481            mMmsView = findViewById(R.id.mms_view);
482            // if mMmsView is still null here, that mean the mms section hasn't been inflated
483
484            if (visible && mMmsView == null) {
485                //inflate the mms view_stub
486                View mmsStub = findViewById(R.id.mms_layout_view_stub);
487                mmsStub.setVisibility(View.VISIBLE);
488                mMmsView = findViewById(R.id.mms_view);
489            }
490        }
491        if (mMmsView != null) {
492            if (mImageView == null) {
493                mImageView = (ImageView) findViewById(R.id.image_view);
494            }
495            if (mSlideShowButton == null) {
496                mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button);
497            }
498            mMmsView.setVisibility(visible ? View.VISIBLE : View.GONE);
499            mImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
500        }
501    }
502
503    private void inflateDownloadControls() {
504        if (mDownloadButton == null) {
505            //inflate the download controls
506            findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE);
507            mDownloadButton = (Button) findViewById(R.id.btn_download_msg);
508            mDownloadingLabel = (TextView) findViewById(R.id.label_downloading);
509        }
510    }
511
512
513    private LineHeightSpan mSpan = new LineHeightSpan() {
514        @Override
515        public void chooseHeight(CharSequence text, int start,
516                int end, int spanstartv, int v, FontMetricsInt fm) {
517            fm.ascent -= 10;
518        }
519    };
520
521    TextAppearanceSpan mTextSmallSpan =
522        new TextAppearanceSpan(mContext, android.R.style.TextAppearance_Small);
523
524    ForegroundColorSpan mColorSpan = null;  // set in ctor
525
526    private CharSequence formatMessage(MessageItem msgItem, String body,
527                                       String subject, Pattern highlight,
528                                       String contentType) {
529        SpannableStringBuilder buf = new SpannableStringBuilder();
530
531        boolean hasSubject = !TextUtils.isEmpty(subject);
532        SmileyParser parser = SmileyParser.getInstance();
533        if (hasSubject) {
534            CharSequence smilizedSubject = parser.addSmileySpans(subject);
535            // Can't use the normal getString() with extra arguments for string replacement
536            // because it doesn't preserve the SpannableText returned by addSmileySpans.
537            // We have to manually replace the %s with our text.
538            buf.append(TextUtils.replace(mContext.getResources().getString(R.string.inline_subject),
539                    new String[] { "%s" }, new CharSequence[] { smilizedSubject }));
540        }
541
542        if (!TextUtils.isEmpty(body)) {
543            // Converts html to spannable if ContentType is "text/html".
544            if (contentType != null && ContentType.TEXT_HTML.equals(contentType)) {
545                buf.append("\n");
546                buf.append(Html.fromHtml(body));
547            } else {
548                if (hasSubject) {
549                    buf.append(" - ");
550                }
551                buf.append(parser.addSmileySpans(body));
552            }
553        }
554
555        if (highlight != null) {
556            Matcher m = highlight.matcher(buf.toString());
557            while (m.find()) {
558                buf.setSpan(new StyleSpan(Typeface.BOLD), m.start(), m.end(), 0);
559            }
560        }
561        return buf;
562    }
563
564    private void drawPlaybackButton(MessageItem msgItem) {
565        switch (msgItem.mAttachmentType) {
566            case WorkingMessage.SLIDESHOW:
567            case WorkingMessage.AUDIO:
568            case WorkingMessage.VIDEO:
569                // Show the 'Play' button and bind message info on it.
570                mSlideShowButton.setTag(msgItem);
571                // Set call-back for the 'Play' button.
572                mSlideShowButton.setOnClickListener(this);
573                mSlideShowButton.setVisibility(View.VISIBLE);
574                setLongClickable(true);
575
576                // When we show the mSlideShowButton, this list item's onItemClickListener doesn't
577                // get called. (It gets set in ComposeMessageActivity:
578                // mMsgListView.setOnItemClickListener) Here we explicitly set the item's
579                // onClickListener. It allows the item to respond to embedded html links and at the
580                // same time, allows the slide show play button to work.
581                setOnClickListener(new OnClickListener() {
582                    @Override
583                    public void onClick(View v) {
584                        onMessageListItemClick();
585                    }
586                });
587                break;
588            default:
589                mSlideShowButton.setVisibility(View.GONE);
590                break;
591        }
592    }
593
594    // OnClick Listener for the playback button
595    @Override
596    public void onClick(View v) {
597        sendMessage(mMessageItem, MSG_LIST_PLAY);
598    }
599
600    private void sendMessage(MessageItem messageItem, int message) {
601        if (mHandler != null) {
602            Message msg = Message.obtain(mHandler, message);
603            msg.obj = messageItem;
604            msg.sendToTarget(); // See ComposeMessageActivity.mMessageListItemHandler.handleMessage
605        }
606    }
607
608    public void onMessageListItemClick() {
609        // If the message is a failed one, clicking it should reload it in the compose view,
610        // regardless of whether it has links in it
611        if (mMessageItem != null &&
612                mMessageItem.isOutgoingMessage() &&
613                mMessageItem.isFailedMessage() ) {
614
615            // Assuming the current message is a failed one, reload it into the compose view so
616            // the user can resend it.
617            sendMessage(mMessageItem, MSG_LIST_EDIT);
618            return;
619        }
620
621        // Check for links. If none, do nothing; if 1, open it; if >1, ask user to pick one
622        final URLSpan[] spans = mBodyTextView.getUrls();
623
624        if (spans.length == 0) {
625            sendMessage(mMessageItem, MSG_LIST_DETAILS);    // show the message details dialog
626        } else if (spans.length == 1) {
627            spans[0].onClick(mBodyTextView);
628        } else {
629            ArrayAdapter<URLSpan> adapter =
630                new ArrayAdapter<URLSpan>(mContext, android.R.layout.select_dialog_item, spans) {
631                @Override
632                public View getView(int position, View convertView, ViewGroup parent) {
633                    View v = super.getView(position, convertView, parent);
634                    try {
635                        URLSpan span = getItem(position);
636                        String url = span.getURL();
637                        Uri uri = Uri.parse(url);
638                        TextView tv = (TextView) v;
639                        Drawable d = mContext.getPackageManager().getActivityIcon(
640                                new Intent(Intent.ACTION_VIEW, uri));
641                        if (d != null) {
642                            d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight());
643                            tv.setCompoundDrawablePadding(10);
644                            tv.setCompoundDrawables(d, null, null, null);
645                        }
646                        final String telPrefix = "tel:";
647                        if (url.startsWith(telPrefix)) {
648                            if ((mDefaultCountryIso == null) || mDefaultCountryIso.isEmpty()) {
649                                url = url.substring(telPrefix.length());
650                            }
651                            else {
652                                url = PhoneNumberUtils.formatNumber(
653                                        url.substring(telPrefix.length()), mDefaultCountryIso);
654                            }
655                        }
656                        tv.setText(url);
657                    } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
658                        // it's ok if we're unable to set the drawable for this view - the user
659                        // can still use it
660                    }
661                    return v;
662                }
663            };
664
665            AlertDialog.Builder b = new AlertDialog.Builder(mContext);
666
667            DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() {
668                @Override
669                public final void onClick(DialogInterface dialog, int which) {
670                    if (which >= 0) {
671                        spans[which].onClick(mBodyTextView);
672                    }
673                    dialog.dismiss();
674                }
675            };
676
677            b.setTitle(R.string.select_link_title);
678            b.setCancelable(true);
679            b.setAdapter(adapter, click);
680
681            b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
682                @Override
683                public final void onClick(DialogInterface dialog, int which) {
684                    dialog.dismiss();
685                }
686            });
687
688            b.show();
689        }
690    }
691
692    private void setOnClickListener(final MessageItem msgItem) {
693        switch(msgItem.mAttachmentType) {
694            case WorkingMessage.IMAGE:
695            case WorkingMessage.VIDEO:
696                mImageView.setOnClickListener(new OnClickListener() {
697                    @Override
698                    public void onClick(View v) {
699                        sendMessage(msgItem, MSG_LIST_PLAY);
700                    }
701                });
702                mImageView.setOnLongClickListener(new OnLongClickListener() {
703                    @Override
704                    public boolean onLongClick(View v) {
705                        return v.showContextMenu();
706                    }
707                });
708                break;
709
710            default:
711                mImageView.setOnClickListener(null);
712                break;
713            }
714    }
715
716    private void drawRightStatusIndicator(MessageItem msgItem) {
717        // Locked icon
718        if (msgItem.mLocked) {
719            mLockedIndicator.setImageResource(R.drawable.ic_lock_message_sms);
720            mLockedIndicator.setVisibility(View.VISIBLE);
721        } else {
722            mLockedIndicator.setVisibility(View.GONE);
723        }
724
725        // Delivery icon - we can show a failed icon for both sms and mms, but for an actual
726        // delivery, we only show the icon for sms. We don't have the information here in mms to
727        // know whether the message has been delivered. For mms, msgItem.mDeliveryStatus set
728        // to MessageItem.DeliveryStatus.RECEIVED simply means the setting requesting a
729        // delivery report was turned on when the message was sent. Yes, it's confusing!
730        if ((msgItem.isOutgoingMessage() && msgItem.isFailedMessage()) ||
731                msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) {
732            mDeliveredIndicator.setImageResource(R.drawable.ic_list_alert_sms_failed);
733            mDeliveredIndicator.setVisibility(View.VISIBLE);
734        } else if (msgItem.isSms() &&
735                msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) {
736            mDeliveredIndicator.setImageResource(R.drawable.ic_sms_mms_delivered);
737            mDeliveredIndicator.setVisibility(View.VISIBLE);
738        } else {
739            mDeliveredIndicator.setVisibility(View.GONE);
740        }
741
742        // Message details icon - this icon is shown both for sms and mms messages. For mms,
743        // we show the icon if the read report or delivery report setting was set when the
744        // message was sent. Showing the icon tells the user there's more information
745        // by selecting the "View report" menu.
746        if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport
747                || (msgItem.isMms() &&
748                        msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED)) {
749            mDetailsIndicator.setImageResource(R.drawable.ic_sms_mms_details);
750            mDetailsIndicator.setVisibility(View.VISIBLE);
751        } else {
752            mDetailsIndicator.setVisibility(View.GONE);
753        }
754    }
755
756    @Override
757    public void setImageRegionFit(String fit) {
758        // TODO Auto-generated method stub
759    }
760
761    @Override
762    public void setImageVisibility(boolean visible) {
763        // TODO Auto-generated method stub
764    }
765
766    @Override
767    public void setText(String name, String text) {
768        // TODO Auto-generated method stub
769    }
770
771    @Override
772    public void setTextVisibility(boolean visible) {
773        // TODO Auto-generated method stub
774    }
775
776    @Override
777    public void setVideo(String name, Uri uri) {
778    }
779
780    @Override
781    public void setVideoThumbnail(String name, Bitmap bitmap) {
782        showMmsView(true);
783
784        try {
785            mImageView.setImageBitmap(bitmap);
786            mImageView.setVisibility(VISIBLE);
787        } catch (java.lang.OutOfMemoryError e) {
788            Log.e(TAG, "setVideo: out of memory: ", e);
789        }
790    }
791
792    @Override
793    public void setVideoVisibility(boolean visible) {
794        // TODO Auto-generated method stub
795    }
796
797    @Override
798    public void stopAudio() {
799        // TODO Auto-generated method stub
800    }
801
802    @Override
803    public void stopVideo() {
804        // TODO Auto-generated method stub
805    }
806
807    @Override
808    public void reset() {
809    }
810
811    @Override
812    public void setVisibility(boolean visible) {
813        // TODO Auto-generated method stub
814    }
815
816    @Override
817    public void pauseAudio() {
818        // TODO Auto-generated method stub
819
820    }
821
822    @Override
823    public void pauseVideo() {
824        // TODO Auto-generated method stub
825
826    }
827
828    @Override
829    public void seekAudio(int seekTo) {
830        // TODO Auto-generated method stub
831
832    }
833
834    @Override
835    public void seekVideo(int seekTo) {
836        // TODO Auto-generated method stub
837
838    }
839}
840