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