MessageListItem.java revision a1a71dd05a759715c82bfc8bcaa12bbe2d22c751
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 com.android.mms.R;
21import com.android.mms.transaction.Transaction;
22import com.android.mms.transaction.TransactionBundle;
23import com.android.mms.transaction.TransactionService;
24import com.android.mms.util.DownloadManager;
25import com.android.mms.util.SmileyParser;
26
27import com.google.android.mms.pdu.PduHeaders;
28
29import android.app.AlertDialog;
30import android.content.Context;
31import android.content.DialogInterface;
32import android.content.Intent;
33import android.graphics.Bitmap;
34import android.graphics.BitmapFactory;
35import android.graphics.Canvas;
36import android.graphics.Paint;
37import android.graphics.Typeface;
38import android.graphics.drawable.Drawable;
39import android.media.MediaMetadataRetriever;
40import android.media.MediaPlayer;
41import android.net.Uri;
42import android.os.Handler;
43import android.os.Message;
44import android.provider.Browser;
45import android.provider.Telephony.Mms;
46import android.provider.Telephony.MmsSms;
47import android.provider.Telephony.Sms;
48import android.telephony.PhoneNumberUtils;
49import android.text.Spannable;
50import android.text.SpannableStringBuilder;
51import android.text.TextUtils;
52import android.text.method.HideReturnsTransformationMethod;
53import android.text.style.AbsoluteSizeSpan;
54import android.text.style.BackgroundColorSpan;
55import android.text.style.ForegroundColorSpan;
56import android.text.style.LineBackgroundSpan;
57import android.text.style.StyleSpan;
58import android.text.style.URLSpan;
59import android.util.AttributeSet;
60import android.util.Log;
61import android.view.View;
62import android.view.ViewGroup;
63import android.view.View.OnClickListener;
64import android.widget.ArrayAdapter;
65import android.widget.Button;
66import android.widget.ImageButton;
67import android.widget.ImageView;
68import android.widget.LinearLayout;
69import android.widget.TextView;
70
71import java.io.IOException;
72import java.util.Map;
73
74/**
75 * This class provides view of a message in the messages list.
76 */
77public class MessageListItem extends LinearLayout implements
78        SlideViewInterface, OnClickListener {
79    public static final String EXTRA_URLS = "com.android.mms.ExtraUrls";
80
81    private static final String TAG = "MessageListItem";
82    private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
83
84    static final int MSG_LIST_EDIT_MMS   = 1;
85    static final int MSG_LIST_EDIT_SMS   = 2;
86
87    private View mMsgListItem;
88    private View mMmsView;
89    private ImageView mImageView;
90    private ImageView mRightStatusIndicator;
91    private ImageButton mSlideShowButton;
92    private TextView mBodyTextView;
93    private Button mDownloadButton;
94    private TextView mDownloadingLabel;
95    private Handler mHandler;
96    private MessageItem mMessageItem;
97
98    public MessageListItem(Context context) {
99        super(context);
100    }
101
102    public MessageListItem(Context context, AttributeSet attrs) {
103        super(context, attrs);
104    }
105
106    @Override
107    protected void onFinishInflate() {
108        super.onFinishInflate();
109
110        mMsgListItem = findViewById(R.id.msg_list_item);
111        mBodyTextView = (TextView) findViewById(R.id.text_view);
112        mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator);
113    }
114
115    public void bind(MessageItem msgItem) {
116        mMessageItem = msgItem;
117
118        setLongClickable(false);
119
120        switch (msgItem.mMessageType) {
121            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
122                bindNotifInd(msgItem);
123                break;
124            default:
125                bindCommonMessage(msgItem);
126                break;
127        }
128    }
129
130    public MessageItem getMessageItem() {
131        return mMessageItem;
132    }
133
134    public void setMsgListItemHandler(Handler handler) {
135        mHandler = handler;
136    }
137
138    private void bindNotifInd(final MessageItem msgItem) {
139        hideMmsViewIfNeeded();
140
141        String msgSizeText = mContext.getString(R.string.message_size_label)
142                                + String.valueOf((msgItem.mMessageSize + 1023) / 1024)
143                                + mContext.getString(R.string.kilobyte);
144
145        mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject,
146                                            msgSizeText + "\n" + msgItem.mTimestamp));
147
148        int state = DownloadManager.getInstance().getState(msgItem.mMessageUri);
149        switch (state) {
150            case DownloadManager.STATE_DOWNLOADING:
151                inflateDownloadControls();
152                mDownloadingLabel.setVisibility(View.VISIBLE);
153                mDownloadButton.setVisibility(View.GONE);
154                break;
155            case DownloadManager.STATE_UNSTARTED:
156            case DownloadManager.STATE_TRANSIENT_FAILURE:
157            case DownloadManager.STATE_PERMANENT_FAILURE:
158            default:
159                setLongClickable(true);
160                inflateDownloadControls();
161                mDownloadingLabel.setVisibility(View.GONE);
162                mDownloadButton.setVisibility(View.VISIBLE);
163                mDownloadButton.setOnClickListener(new OnClickListener() {
164                    public void onClick(View v) {
165                        mDownloadingLabel.setVisibility(View.VISIBLE);
166                        mDownloadButton.setVisibility(View.GONE);
167                        Intent intent = new Intent(mContext, TransactionService.class);
168                        intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString());
169                        intent.putExtra(TransactionBundle.TRANSACTION_TYPE,
170                                Transaction.RETRIEVE_TRANSACTION);
171                        mContext.startService(intent);
172                    }
173                });
174                break;
175        }
176
177        // Hide the error indicator.
178        mRightStatusIndicator.setVisibility(View.GONE);
179
180        drawLeftStatusIndicator(msgItem.mBoxId);
181    }
182
183    private void bindCommonMessage(final MessageItem msgItem) {
184        if (mDownloadButton != null) {
185            mDownloadButton.setVisibility(View.GONE);
186            mDownloadingLabel.setVisibility(View.GONE);
187        }
188        // Since the message text should be concatenated with the sender's
189        // address(or name), I have to display it here instead of
190        // displaying it by the Presenter.
191        mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
192
193        // Get and/or lazily set the formatted message from/on the
194        // MessageItem.  Because the MessageItem instances come from a
195        // cache (currently of size ~50), the hit rate on avoiding the
196        // expensive formatMessage() call is very high.
197        CharSequence formattedMessage = msgItem.getCachedFormattedMessage();
198        if (formattedMessage == null) {
199            formattedMessage = formatMessage(msgItem.mContact, msgItem.mBody,
200                                             msgItem.mSubject, msgItem.mTimestamp);
201            msgItem.setCachedFormattedMessage(formattedMessage);
202        }
203        mBodyTextView.setText(formattedMessage);
204
205        if (msgItem.isSms()) {
206            hideMmsViewIfNeeded();
207        } else {
208            Presenter presenter = PresenterFactory.getPresenter(
209                    "MmsThumbnailPresenter", mContext,
210                    this, msgItem.mSlideshow);
211            presenter.present();
212
213            if (msgItem.mAttachmentType != AttachmentEditor.TEXT_ONLY) {
214                inflateMmsView();
215                mMmsView.setVisibility(View.VISIBLE);
216                setOnClickListener(msgItem);
217                drawPlaybackButton(msgItem);
218            } else {
219                hideMmsViewIfNeeded();
220            }
221        }
222
223        drawLeftStatusIndicator(msgItem.mBoxId);
224        drawRightStatusIndicator(msgItem);
225    }
226
227    private void hideMmsViewIfNeeded() {
228        if (mMmsView != null) {
229            mMmsView.setVisibility(View.GONE);
230        }
231    }
232
233    public void startAudio() {
234        // TODO Auto-generated method stub
235    }
236
237    public void startVideo() {
238        // TODO Auto-generated method stub
239    }
240
241    public void setAudio(Uri audio, String name, Map<String, ?> extras) {
242        // TODO Auto-generated method stub
243    }
244
245    public void setImage(String name, Bitmap bitmap) {
246        inflateMmsView();
247
248        if (null == bitmap) {
249            bitmap = BitmapFactory.decodeResource(getResources(),
250                    R.drawable.ic_missing_thumbnail_picture);
251        }
252        mImageView.setImageBitmap(bitmap);
253        mImageView.setVisibility(VISIBLE);
254    }
255
256    private void inflateMmsView() {
257        if (mMmsView == null) {
258            //inflate the surrounding view_stub
259            findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE);
260
261            mMmsView = findViewById(R.id.mms_view);
262            mImageView = (ImageView) findViewById(R.id.image_view);
263            mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button);
264        }
265    }
266
267    private void inflateDownloadControls() {
268        if (mDownloadButton == null) {
269            //inflate the download controls
270            findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE);
271            mDownloadButton = (Button) findViewById(R.id.btn_download_msg);
272            mDownloadingLabel = (TextView) findViewById(R.id.label_downloading);
273        }
274    }
275
276    private CharSequence formatMessage(String contact, String body, String subject,
277                                       String timestamp) {
278        SpannableStringBuilder buf = new SpannableStringBuilder(contact);
279        buf.append(": ");
280        buf.setSpan(STYLE_BOLD, 0, buf.length(),
281                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
282
283        boolean hasSubject = !TextUtils.isEmpty(subject);
284        if (hasSubject) {
285            buf.append(mContext.getResources().getString(R.string.inline_subject, subject));
286        }
287
288        if (!TextUtils.isEmpty(body)) {
289            if (hasSubject) {
290                buf.append(" - ");
291            }
292            SmileyParser parser = SmileyParser.getInstance();
293            buf.append(parser.addSmileySpans(body));
294        }
295
296        buf.append("\n");
297        int startOffset = buf.length();
298
299        // put a one pixel high spacer line between the message and the time stamp as requested
300        // by the spec.
301        buf.append("\n");
302        buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(),
303                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
304
305        startOffset = buf.length();
306        buf.append(timestamp);
307        buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(),
308                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
309        // Make the timestamp text not as dark
310        int color = mContext.getResources().getColor(R.color.timestamp_color);
311        buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(),
312                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
313
314        return buf;
315    }
316
317    private void drawPlaybackButton(MessageItem msgItem) {
318        switch (msgItem.mAttachmentType) {
319            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
320            case AttachmentEditor.AUDIO_ATTACHMENT:
321            case AttachmentEditor.VIDEO_ATTACHMENT:
322                // Show the 'Play' button and bind message info on it.
323                mSlideShowButton.setTag(msgItem);
324                // Set call-back for the 'Play' button.
325                mSlideShowButton.setOnClickListener(this);
326                mSlideShowButton.setVisibility(View.VISIBLE);
327                setLongClickable(true);
328                break;
329            default:
330                mSlideShowButton.setVisibility(View.GONE);
331                break;
332        }
333    }
334
335    // OnClick Listener for the playback button
336    public void onClick(View v) {
337        MessageItem mi = (MessageItem) v.getTag();
338        switch (mi.mAttachmentType) {
339            case AttachmentEditor.VIDEO_ATTACHMENT:
340                MessageUtils.viewSimpleSlideshow(mContext, mi.mSlideshow);
341                break;
342            case AttachmentEditor.AUDIO_ATTACHMENT:
343            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
344                Intent intent = new Intent(
345                        mContext, SlideshowActivity.class);
346                intent.setData(mi.mMessageUri);
347                mContext.startActivity(intent);
348                break;
349        }
350    }
351
352    public void onMessageListItemClick() {
353        URLSpan[] spans = mBodyTextView.getUrls();
354
355        if (spans.length == 0) {
356            // Do nothing.
357        } else if (spans.length == 1) {
358            Uri uri = Uri.parse(spans[0].getURL());
359            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
360
361            mContext.startActivity(intent);
362        } else {
363            final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans);
364
365            ArrayAdapter<String> adapter =
366                new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) {
367                public View getView(int position, View convertView, ViewGroup parent) {
368                    View v = super.getView(position, convertView, parent);
369                    try {
370                        String url = getItem(position).toString();
371                        TextView tv = (TextView) v;
372                        Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
373                        if (d != null) {
374                            d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight());
375                            tv.setCompoundDrawablePadding(10);
376                            tv.setCompoundDrawables(d, null, null, null);
377                        }
378                        final String telPrefix = "tel:";
379                        if (url.startsWith(telPrefix)) {
380                            url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length()));
381                        }
382                        tv.setText(url);
383                    } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
384                        ;
385                    }
386                    return v;
387                }
388            };
389
390            AlertDialog.Builder b = new AlertDialog.Builder(mContext);
391
392            DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() {
393                public final void onClick(DialogInterface dialog, int which) {
394                    if (which >= 0) {
395                        Uri uri = Uri.parse(urls.get(which));
396                        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
397                        intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
398                        mContext.startActivity(intent);
399                    }
400                }
401            };
402
403            b.setTitle(R.string.select_link_title);
404            b.setCancelable(true);
405            b.setAdapter(adapter, click);
406
407            b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
408                public final void onClick(DialogInterface dialog, int which) {
409                    dialog.dismiss();
410                }
411            });
412
413            b.show();
414        }
415    }
416
417
418    private void setOnClickListener(final MessageItem msgItem) {
419        switch(msgItem.mAttachmentType) {
420        case AttachmentEditor.IMAGE_ATTACHMENT:
421        case AttachmentEditor.VIDEO_ATTACHMENT:
422            mImageView.setOnClickListener(new OnClickListener() {
423                public void onClick(View v) {
424                    MessageUtils.viewSimpleSlideshow(mContext, msgItem.mSlideshow);
425                }
426            });
427            mImageView.setOnLongClickListener(new OnLongClickListener() {
428                public boolean onLongClick(View v) {
429                    return v.showContextMenu();
430                }
431            });
432            break;
433
434        default:
435            mImageView.setOnClickListener(null);
436            break;
437        }
438    }
439
440    private void drawLeftStatusIndicator(int msgBoxId) {
441        switch (msgBoxId) {
442            case Mms.MESSAGE_BOX_INBOX:
443                mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue);
444                break;
445
446            case Mms.MESSAGE_BOX_DRAFTS:
447            case Sms.MESSAGE_TYPE_FAILED:
448            case Sms.MESSAGE_TYPE_QUEUED:
449            case Mms.MESSAGE_BOX_OUTBOX:
450                mMsgListItem.setBackgroundResource(R.drawable.listitem_background);
451                break;
452
453            default:
454                mMsgListItem.setBackgroundResource(R.drawable.listitem_background);
455                break;
456        }
457    }
458
459    public static boolean isFailedMessage(MessageItem msgItem) {
460        boolean isFailedMms = msgItem.isMms()
461                            && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT);
462        boolean isFailedSms = msgItem.isSms()
463                            && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED);
464        return isFailedMms || isFailedSms;
465    }
466
467    private void setErrorIndicatorClickListener(final MessageItem msgItem) {
468        String type = msgItem.mType;
469        final int what;
470        if (type.equals("sms")) {
471            what = MSG_LIST_EDIT_SMS;
472        } else {
473            what = MSG_LIST_EDIT_MMS;
474        }
475        mRightStatusIndicator.setOnClickListener(new OnClickListener() {
476            public void onClick(View v) {
477                if (null != mHandler) {
478                    Message msg = Message.obtain(mHandler, what);
479                    msg.obj = new Long(msgItem.mMsgId);
480                    msg.sendToTarget();
481                }
482            }
483        });
484    }
485
486    private void drawRightStatusIndicator(MessageItem msgItem) {
487        if (msgItem.isOutgoingMessage()) {
488            if (isFailedMessage(msgItem)) {
489                mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered);
490                setErrorIndicatorClickListener(msgItem);
491            } else {
492                mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending);
493            }
494            mRightStatusIndicator.setVisibility(View.VISIBLE);
495        } else if (msgItem.mDeliveryReport || msgItem.mReadReport) {
496            mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details);
497            mRightStatusIndicator.setVisibility(View.VISIBLE);
498        } else {
499            mRightStatusIndicator.setVisibility(View.GONE);
500        }
501    }
502
503    public void setImageRegionFit(String fit) {
504        // TODO Auto-generated method stub
505    }
506
507    public void setImageVisibility(boolean visible) {
508        // TODO Auto-generated method stub
509    }
510
511    public void setText(String name, String text) {
512        // TODO Auto-generated method stub
513    }
514
515    public void setTextVisibility(boolean visible) {
516        // TODO Auto-generated method stub
517    }
518
519    public void setVideo(String name, Uri video) {
520        inflateMmsView();
521        Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video);
522        if (null == bitmap) {
523            bitmap = BitmapFactory.decodeResource(getResources(),
524                    R.drawable.ic_missing_thumbnail_video);
525        }
526        mImageView.setImageBitmap(bitmap);
527        mImageView.setVisibility(VISIBLE);
528    }
529
530    public void setVideoVisibility(boolean visible) {
531        // TODO Auto-generated method stub
532    }
533
534    public void stopAudio() {
535        // TODO Auto-generated method stub
536    }
537
538    public void stopVideo() {
539        // TODO Auto-generated method stub
540    }
541
542    public void reset() {
543        if (mImageView != null) {
544            mImageView.setVisibility(GONE);
545        }
546    }
547
548    public void setVisibility(boolean visible) {
549        // TODO Auto-generated method stub
550    }
551
552    public void pauseAudio() {
553        // TODO Auto-generated method stub
554
555    }
556
557    public void pauseVideo() {
558        // TODO Auto-generated method stub
559
560    }
561
562    public void seekAudio(int seekTo) {
563        // TODO Auto-generated method stub
564
565    }
566
567    public void seekVideo(int seekTo) {
568        // TODO Auto-generated method stub
569
570    }
571}
572