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