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