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