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