• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
MessageListItem(Context context)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 
MessageListItem(Context context, AttributeSet attrs)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
onFinishInflate()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 
bind(MessageItem msgItem, boolean convHasMultiRecipients, int position)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 
unbind()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 
getMessageItem()192     public MessageItem getMessageItem() {
193         return mMessageItem;
194     }
195 
setMsgListItemHandler(Handler handler)196     public void setMsgListItemHandler(Handler handler) {
197         mHandler = handler;
198     }
199 
bindNotifInd()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 
buildTimestampLine(String timestamp)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 
showDownloadingAttachment()275     private void showDownloadingAttachment() {
276         inflateDownloadControls();
277         mDownloadingLabel.setVisibility(View.VISIBLE);
278         mDownloadButton.setVisibility(View.GONE);
279     }
280 
updateAvatarView(String addr, boolean isSelf)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 
bindCommonMessage(final boolean sameItem)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 
ImageLoadedCallback(MessageListItem listItem)430         public ImageLoadedCallback(MessageListItem listItem) {
431             mListItem = listItem;
432             mMessageId = listItem.getMessageItem().getMessageId();
433         }
434 
reset(MessageListItem listItem)435         public void reset(MessageListItem listItem) {
436             mMessageId = listItem.getMessageItem().getMessageId();
437         }
438 
onItemLoaded(ImageLoaded imageLoaded, Throwable exception)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
startAudio()457     public void startAudio() {
458         // TODO Auto-generated method stub
459     }
460 
461     @Override
startVideo()462     public void startVideo() {
463         // TODO Auto-generated method stub
464     }
465 
466     @Override
setAudio(Uri audio, String name, Map<String, ?> extras)467     public void setAudio(Uri audio, String name, Map<String, ?> extras) {
468         // TODO Auto-generated method stub
469     }
470 
471     @Override
setImage(String name, Bitmap bitmap)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 
showMmsView(boolean visible)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 
inflateDownloadControls()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 
formatMessage(MessageItem msgItem, String body, String subject, Pattern highlight, String contentType)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 
drawPlaybackButton(MessageItem msgItem)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
onClick(View v)600     public void onClick(View v) {
601         sendMessage(mMessageItem, MSG_LIST_PLAY);
602     }
603 
sendMessage(MessageItem messageItem, int message)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 
onMessageListItemClick()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 
setOnClickListener(final MessageItem msgItem)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 
drawRightStatusIndicator(MessageItem msgItem)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
setImageRegionFit(String fit)761     public void setImageRegionFit(String fit) {
762         // TODO Auto-generated method stub
763     }
764 
765     @Override
setImageVisibility(boolean visible)766     public void setImageVisibility(boolean visible) {
767         // TODO Auto-generated method stub
768     }
769 
770     @Override
setText(String name, String text)771     public void setText(String name, String text) {
772         // TODO Auto-generated method stub
773     }
774 
775     @Override
setTextVisibility(boolean visible)776     public void setTextVisibility(boolean visible) {
777         // TODO Auto-generated method stub
778     }
779 
780     @Override
setVideo(String name, Uri uri)781     public void setVideo(String name, Uri uri) {
782     }
783 
784     @Override
setVideoThumbnail(String name, Bitmap bitmap)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
setVideoVisibility(boolean visible)797     public void setVideoVisibility(boolean visible) {
798         // TODO Auto-generated method stub
799     }
800 
801     @Override
stopAudio()802     public void stopAudio() {
803         // TODO Auto-generated method stub
804     }
805 
806     @Override
stopVideo()807     public void stopVideo() {
808         // TODO Auto-generated method stub
809     }
810 
811     @Override
reset()812     public void reset() {
813     }
814 
815     @Override
setVisibility(boolean visible)816     public void setVisibility(boolean visible) {
817         // TODO Auto-generated method stub
818     }
819 
820     @Override
pauseAudio()821     public void pauseAudio() {
822         // TODO Auto-generated method stub
823 
824     }
825 
826     @Override
pauseVideo()827     public void pauseVideo() {
828         // TODO Auto-generated method stub
829 
830     }
831 
832     @Override
seekAudio(int seekTo)833     public void seekAudio(int seekTo) {
834         // TODO Auto-generated method stub
835 
836     }
837 
838     @Override
seekVideo(int seekTo)839     public void seekVideo(int seekTo) {
840         // TODO Auto-generated method stub
841 
842     }
843 }
844