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