MessageListItem.java revision e7c8092e952d9d5edbc1c0b62e5f872c4d21d2b4
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        CharSequence template = mContext.getResources().getText(R.string.name_colon);
280        SpannableStringBuilder buf =
281            new SpannableStringBuilder(TextUtils.replace(template,
282                new String[] { "%s" },
283                new CharSequence[] { contact }));
284
285        boolean hasSubject = !TextUtils.isEmpty(subject);
286        if (hasSubject) {
287            buf.append(mContext.getResources().getString(R.string.inline_subject, subject));
288        }
289
290        if (!TextUtils.isEmpty(body)) {
291            if (hasSubject) {
292                buf.append(" - ");
293            }
294            SmileyParser parser = SmileyParser.getInstance();
295            buf.append(parser.addSmileySpans(body));
296        }
297
298        buf.append("\n");
299        int startOffset = buf.length();
300
301        // put a one pixel high spacer line between the message and the time stamp as requested
302        // by the spec.
303        buf.append("\n");
304        buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(),
305                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
306
307        startOffset = buf.length();
308        buf.append(timestamp);
309        buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(),
310                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
311        // Make the timestamp text not as dark
312        int color = mContext.getResources().getColor(R.color.timestamp_color);
313        buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(),
314                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
315
316        return buf;
317    }
318
319    private void drawPlaybackButton(MessageItem msgItem) {
320        switch (msgItem.mAttachmentType) {
321            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
322            case AttachmentEditor.AUDIO_ATTACHMENT:
323            case AttachmentEditor.VIDEO_ATTACHMENT:
324                // Show the 'Play' button and bind message info on it.
325                mSlideShowButton.setTag(msgItem);
326                // Set call-back for the 'Play' button.
327                mSlideShowButton.setOnClickListener(this);
328                mSlideShowButton.setVisibility(View.VISIBLE);
329                setLongClickable(true);
330                break;
331            default:
332                mSlideShowButton.setVisibility(View.GONE);
333                break;
334        }
335    }
336
337    // OnClick Listener for the playback button
338    public void onClick(View v) {
339        MessageItem mi = (MessageItem) v.getTag();
340        switch (mi.mAttachmentType) {
341            case AttachmentEditor.VIDEO_ATTACHMENT:
342            case AttachmentEditor.AUDIO_ATTACHMENT:
343            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
344                MessageUtils.viewMmsMessageAttachment(mContext,
345                        mi.mMessageUri, mi.mSlideshow, null /* persister */);
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 AttachmentEditor.IMAGE_ATTACHMENT:
420        case AttachmentEditor.VIDEO_ATTACHMENT:
421            mImageView.setOnClickListener(new OnClickListener() {
422                public void onClick(View v) {
423                    MessageUtils.viewMmsMessageAttachment(mContext,
424                        null /* uri */, msgItem.mSlideshow, null /* persister */);
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.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport) {
496            mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details);
497            mRightStatusIndicator.setVisibility(View.VISIBLE);
498        } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.PENDING) {
499            mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_pending);
500            mRightStatusIndicator.setVisibility(View.VISIBLE);
501        } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) {
502            mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered);
503            mRightStatusIndicator.setVisibility(View.VISIBLE);
504        } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) {
505            mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_delivered);
506            mRightStatusIndicator.setVisibility(View.VISIBLE);
507        } else {
508            mRightStatusIndicator.setVisibility(View.GONE);
509        }
510    }
511
512    public void setImageRegionFit(String fit) {
513        // TODO Auto-generated method stub
514    }
515
516    public void setImageVisibility(boolean visible) {
517        // TODO Auto-generated method stub
518    }
519
520    public void setText(String name, String text) {
521        // TODO Auto-generated method stub
522    }
523
524    public void setTextVisibility(boolean visible) {
525        // TODO Auto-generated method stub
526    }
527
528    public void setVideo(String name, Uri video) {
529        inflateMmsView();
530        Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video);
531        if (null == bitmap) {
532            bitmap = BitmapFactory.decodeResource(getResources(),
533                    R.drawable.ic_missing_thumbnail_video);
534        }
535        mImageView.setImageBitmap(bitmap);
536        mImageView.setVisibility(VISIBLE);
537    }
538
539    public void setVideoVisibility(boolean visible) {
540        // TODO Auto-generated method stub
541    }
542
543    public void stopAudio() {
544        // TODO Auto-generated method stub
545    }
546
547    public void stopVideo() {
548        // TODO Auto-generated method stub
549    }
550
551    public void reset() {
552        if (mImageView != null) {
553            mImageView.setVisibility(GONE);
554        }
555    }
556
557    public void setVisibility(boolean visible) {
558        // TODO Auto-generated method stub
559    }
560
561    public void pauseAudio() {
562        // TODO Auto-generated method stub
563
564    }
565
566    public void pauseVideo() {
567        // TODO Auto-generated method stub
568
569    }
570
571    public void seekAudio(int seekTo) {
572        // TODO Auto-generated method stub
573
574    }
575
576    public void seekVideo(int seekTo) {
577        // TODO Auto-generated method stub
578
579    }
580}
581