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