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