MessageListItem.java revision 121083d1d2046ec3f7c50c19366303c797833a4a
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        boolean drawWithBackground = msgItem.mBoxId == Mms.MESSAGE_BOX_INBOX;
145
146        mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject,
147                                            msgSizeText + "\n" + msgItem.mTimestamp,
148                                            drawWithBackground));
149
150        int state = DownloadManager.getInstance().getState(msgItem.mMessageUri);
151        switch (state) {
152            case DownloadManager.STATE_DOWNLOADING:
153                inflateDownloadControls();
154                mDownloadingLabel.setVisibility(View.VISIBLE);
155                mDownloadButton.setVisibility(View.GONE);
156                break;
157            case DownloadManager.STATE_UNSTARTED:
158            case DownloadManager.STATE_TRANSIENT_FAILURE:
159            case DownloadManager.STATE_PERMANENT_FAILURE:
160            default:
161                setLongClickable(true);
162                inflateDownloadControls();
163                mDownloadingLabel.setVisibility(View.GONE);
164                mDownloadButton.setVisibility(View.VISIBLE);
165                mDownloadButton.setOnClickListener(new OnClickListener() {
166                    public void onClick(View v) {
167                        mDownloadingLabel.setVisibility(View.VISIBLE);
168                        mDownloadButton.setVisibility(View.GONE);
169                        Intent intent = new Intent(mContext, TransactionService.class);
170                        intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString());
171                        intent.putExtra(TransactionBundle.TRANSACTION_TYPE,
172                                Transaction.RETRIEVE_TRANSACTION);
173                        mContext.startService(intent);
174                    }
175                });
176                break;
177        }
178
179        // Hide the error indicator.
180        mRightStatusIndicator.setVisibility(View.GONE);
181
182        drawLeftStatusIndicator(msgItem.mBoxId);
183    }
184
185    private void bindCommonMessage(final MessageItem msgItem) {
186        if (mDownloadButton != null) {
187            mDownloadButton.setVisibility(View.GONE);
188            mDownloadingLabel.setVisibility(View.GONE);
189        }
190        // Since the message text should be concatenated with the sender's
191        // address(or name), I have to display it here instead of
192        // displaying it by the Presenter.
193        mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
194
195        boolean drawWithBackground = msgItem.mBoxId == Mms.MESSAGE_BOX_INBOX;
196
197        mBodyTextView.setText(formatMessage(msgItem.mContact, msgItem.mBody,
198                                            msgItem.mSubject, msgItem.mTimestamp,
199                                            drawWithBackground));
200        // TODO part of changing contact names to links
201        //mBodyTextView.setText(formatMessage(msgItem.mAddress, msgItem.mBody));
202
203        if (msgItem.isSms()) {
204            hideMmsViewIfNeeded();
205        } else {
206            Presenter presenter = PresenterFactory.getPresenter(
207                    "MmsThumbnailPresenter", mContext,
208                    this, msgItem.mSlideshow);
209            presenter.present();
210
211            if (msgItem.mAttachmentType != AttachmentEditor.TEXT_ONLY) {
212                inflateMmsView();
213                mMmsView.setVisibility(View.VISIBLE);
214                setOnClickListener(msgItem);
215                drawPlaybackButton(msgItem);
216            } else {
217                hideMmsViewIfNeeded();
218            }
219        }
220
221        drawLeftStatusIndicator(msgItem.mBoxId);
222        drawRightStatusIndicator(msgItem);
223    }
224
225    private void hideMmsViewIfNeeded() {
226        if (mMmsView != null) {
227            mMmsView.setVisibility(View.GONE);
228        }
229    }
230
231    public void startAudio() {
232        // TODO Auto-generated method stub
233    }
234
235    public void startVideo() {
236        // TODO Auto-generated method stub
237    }
238
239    public void setAudio(Uri audio, String name, Map<String, ?> extras) {
240        // TODO Auto-generated method stub
241    }
242
243    public void setImage(String name, Bitmap bitmap) {
244        inflateMmsView();
245
246        mImageView.setImageBitmap(bitmap);
247        mImageView.setVisibility(VISIBLE);
248    }
249
250    private void inflateMmsView() {
251        if (mMmsView == null) {
252            //inflate the surrounding view_stub
253            findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE);
254
255            mMmsView = findViewById(R.id.mms_view);
256            mImageView = (ImageView) findViewById(R.id.image_view);
257            mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button);
258        }
259    }
260
261    private void inflateDownloadControls() {
262        if (mDownloadButton == null) {
263            //inflate the download controls
264            findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE);
265            mDownloadButton = (Button) findViewById(R.id.btn_download_msg);
266            mDownloadingLabel = (TextView) findViewById(R.id.label_downloading);
267        }
268    }
269
270    private CharSequence formatMessage(String contact, String body, String subject,
271                                       String timestamp, boolean drawBackground) {
272        SpannableStringBuilder buf = new SpannableStringBuilder(contact);
273        buf.append(": ");
274        buf.setSpan(STYLE_BOLD, 0, buf.length(),
275                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
276
277        boolean hasSubject = !TextUtils.isEmpty(subject);
278        if (hasSubject) {
279            buf.append(mContext.getResources().getString(R.string.inline_subject, subject));
280        }
281
282        if (!TextUtils.isEmpty(body)) {
283            if (hasSubject) {
284                buf.append(" - ");
285            }
286            SmileyParser parser = SmileyParser.getInstance();
287            buf.append(parser.addSmileySpans(body));
288        }
289
290        buf.append("\n");
291        int startOffset = buf.length();
292
293        // put a one pixel high spacer line between the message and the time stamp as requested
294        // by the spec.
295        buf.append("\n");
296        buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(),
297                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
298
299        startOffset = buf.length();
300        buf.append(timestamp);
301        buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(),
302                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
303        // Make the timestamp text not as dark
304        int color = mContext.getResources().getColor(R.color.timestamp_color);
305        buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(),
306                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
307
308        return buf;
309    }
310
311    private void drawPlaybackButton(MessageItem msgItem) {
312        switch (msgItem.mAttachmentType) {
313            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
314            case AttachmentEditor.AUDIO_ATTACHMENT:
315            case AttachmentEditor.VIDEO_ATTACHMENT:
316                // Show the 'Play' button and bind message info on it.
317                mSlideShowButton.setTag(msgItem);
318                // Set call-back for the 'Play' button.
319                mSlideShowButton.setOnClickListener(this);
320                mSlideShowButton.setVisibility(View.VISIBLE);
321                setLongClickable(true);
322                break;
323            default:
324                mSlideShowButton.setVisibility(View.GONE);
325                break;
326        }
327    }
328
329    // OnClick Listener for the playback button
330    public void onClick(View v) {
331        MessageItem mi = (MessageItem) v.getTag();
332        switch (mi.mAttachmentType) {
333            case AttachmentEditor.AUDIO_ATTACHMENT:
334            case AttachmentEditor.VIDEO_ATTACHMENT:
335            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
336                Intent intent = new Intent(
337                        mContext, SlideshowActivity.class);
338                intent.setData(mi.mMessageUri);
339                mContext.startActivity(intent);
340                break;
341        }
342    }
343
344    public void onMessageListItemClick() {
345        URLSpan[] spans = mBodyTextView.getUrls();
346
347        if (spans.length == 0) {
348            // Do nothing.
349        } else if (spans.length == 1) {
350            Uri uri = Uri.parse(spans[0].getURL());
351            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
352
353            mContext.startActivity(intent);
354        } else {
355            final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans);
356
357            ArrayAdapter<String> adapter =
358                new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) {
359                public View getView(int position, View convertView, ViewGroup parent) {
360                    View v = super.getView(position, convertView, parent);
361                    try {
362                        String url = getItem(position).toString();
363                        TextView tv = (TextView) v;
364                        Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
365                        if (d != null) {
366                            d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight());
367                            tv.setCompoundDrawablePadding(10);
368                            tv.setCompoundDrawables(d, null, null, null);
369                        }
370                        final String telPrefix = "tel:";
371                        if (url.startsWith(telPrefix)) {
372                            url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length()));
373                        }
374                        tv.setText(url);
375                    } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
376                        ;
377                    }
378                    return v;
379                }
380            };
381
382            AlertDialog.Builder b = new AlertDialog.Builder(mContext);
383
384            DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() {
385                public final void onClick(DialogInterface dialog, int which) {
386                    if (which >= 0) {
387                        Uri uri = Uri.parse(urls.get(which));
388                        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
389                        intent.addCategory(Intent.CATEGORY_BROWSABLE);
390                        mContext.startActivity(intent);
391                    }
392                }
393            };
394
395            b.setTitle(R.string.select_link_title);
396            b.setCancelable(true);
397            b.setAdapter(adapter, click);
398
399            b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
400                public final void onClick(DialogInterface dialog, int which) {
401                    dialog.dismiss();
402                }
403            });
404
405            b.show();
406        }
407    }
408
409
410    private void setOnClickListener(final MessageItem msgItem) {
411        if (msgItem.mAttachmentType == AttachmentEditor.IMAGE_ATTACHMENT) {
412            mImageView.setOnClickListener(new OnClickListener() {
413                public void onClick(View v) {
414                    // FIXME: Use SlideshowActivity to view image for the time being.
415                    // As described in UI spec, pressing an inline attachment will
416                    // open up the full view of the attachment in its associated app
417                    // (here should the pictures app).
418                    // But the <ViewImage> would only show images in MediaStore.
419                    // Should we save a copy to MediaStore temporarily for displaying?
420                    Intent intent = new Intent(mContext, SlideshowActivity.class);
421                    intent.setData(msgItem.mMessageUri);
422                    mContext.startActivity(intent);
423                }
424            });
425            mImageView.setOnLongClickListener(new OnLongClickListener() {
426                public boolean onLongClick(View v) {
427                    return v.showContextMenu();
428                }
429            });
430        } else {
431            mImageView.setOnClickListener(null);
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