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