MessageListItem.java revision 440071bb40c9c7bac5870a8bb3110edbe665c702
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;
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.Typeface;
37import android.graphics.drawable.Drawable;
38import android.net.Uri;
39import android.os.Handler;
40import android.os.Message;
41import android.provider.Browser;
42import android.provider.Telephony.Mms;
43import android.provider.Telephony.MmsSms;
44import android.provider.Telephony.Sms;
45import android.telephony.PhoneNumberUtils;
46import android.text.Spannable;
47import android.text.SpannableStringBuilder;
48import android.text.TextUtils;
49import android.text.method.HideReturnsTransformationMethod;
50import android.text.style.AbsoluteSizeSpan;
51import android.text.style.ForegroundColorSpan;
52import android.text.style.StyleSpan;
53import android.text.style.URLSpan;
54import android.util.AttributeSet;
55import android.util.Log;
56import android.view.View;
57import android.view.ViewGroup;
58import android.view.View.OnClickListener;
59import android.widget.ArrayAdapter;
60import android.widget.Button;
61import android.widget.ImageButton;
62import android.widget.ImageView;
63import android.widget.LinearLayout;
64import android.widget.TextView;
65
66import java.util.Map;
67
68/**
69 * This class provides view of a message in the messages list.
70 */
71public class MessageListItem extends LinearLayout implements
72        SlideViewInterface, OnClickListener {
73    public static final String EXTRA_URLS = "com.android.mms.ExtraUrls";
74
75    private static final String TAG = "MessageListItem";
76    private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
77
78    static final int MSG_LIST_EDIT_MMS   = 1;
79    static final int MSG_LIST_EDIT_SMS   = 2;
80
81    private View mMsgListItem;
82    private View mMmsView;
83    private ImageView mImageView;
84    private ImageView mRightStatusIndicator;
85    private ImageButton mSlideShowButton;
86    private TextView mBodyTextView;
87    private Button mDownloadButton;
88    private TextView mDownloadingLabel;
89    private Handler mHandler;
90    private MessageItem mMessageItem;
91
92    public MessageListItem(Context context) {
93        super(context);
94    }
95
96    public MessageListItem(Context context, AttributeSet attrs) {
97        super(context, attrs);
98    }
99
100    @Override
101    protected void onFinishInflate() {
102        super.onFinishInflate();
103
104        mMsgListItem = findViewById(R.id.msg_list_item);
105        mBodyTextView = (TextView) findViewById(R.id.text_view);
106        mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator);
107    }
108
109    public void bind(MessageItem msgItem) {
110        mMessageItem = msgItem;
111
112        setLongClickable(false);
113
114        switch (msgItem.mMessageType) {
115            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
116                bindNotifInd(msgItem);
117                break;
118            default:
119                bindCommonMessage(msgItem);
120                break;
121        }
122    }
123
124    public MessageItem getMessageItem() {
125        return mMessageItem;
126    }
127
128    public void setMsgListItemHandler(Handler handler) {
129        mHandler = handler;
130    }
131
132    private void bindNotifInd(final MessageItem msgItem) {
133        hideMmsViewIfNeeded();
134
135        String msgSizeText = mContext.getString(R.string.message_size_label)
136                                + String.valueOf((msgItem.mMessageSize + 1023) / 1024)
137                                + mContext.getString(R.string.kilobyte);
138
139        mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject,
140                                            msgSizeText + "\n" + msgItem.mTimestamp));
141
142        int state = DownloadManager.getInstance().getState(msgItem.mMessageUri);
143        switch (state) {
144            case DownloadManager.STATE_DOWNLOADING:
145                inflateDownloadControls();
146                mDownloadingLabel.setVisibility(View.VISIBLE);
147                mDownloadButton.setVisibility(View.GONE);
148                break;
149            case DownloadManager.STATE_UNSTARTED:
150            case DownloadManager.STATE_TRANSIENT_FAILURE:
151            case DownloadManager.STATE_PERMANENT_FAILURE:
152            default:
153                setLongClickable(true);
154                inflateDownloadControls();
155                mDownloadingLabel.setVisibility(View.GONE);
156                mDownloadButton.setVisibility(View.VISIBLE);
157                mDownloadButton.setOnClickListener(new OnClickListener() {
158                    public void onClick(View v) {
159                        mDownloadingLabel.setVisibility(View.VISIBLE);
160                        mDownloadButton.setVisibility(View.GONE);
161                        Intent intent = new Intent(mContext, TransactionService.class);
162                        intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString());
163                        intent.putExtra(TransactionBundle.TRANSACTION_TYPE,
164                                Transaction.RETRIEVE_TRANSACTION);
165                        mContext.startService(intent);
166                    }
167                });
168                break;
169        }
170
171        // Hide the error indicator.
172        mRightStatusIndicator.setVisibility(View.GONE);
173
174        drawLeftStatusIndicator(msgItem.mBoxId);
175    }
176
177    private void bindCommonMessage(final MessageItem msgItem) {
178        if (mDownloadButton != null) {
179            mDownloadButton.setVisibility(View.GONE);
180            mDownloadingLabel.setVisibility(View.GONE);
181        }
182        // Since the message text should be concatenated with the sender's
183        // address(or name), I have to display it here instead of
184        // displaying it by the Presenter.
185        mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
186
187        // Get and/or lazily set the formatted message from/on the
188        // MessageItem.  Because the MessageItem instances come from a
189        // cache (currently of size ~50), the hit rate on avoiding the
190        // expensive formatMessage() call is very high.
191        CharSequence formattedMessage = msgItem.getCachedFormattedMessage();
192        if (formattedMessage == null) {
193            formattedMessage = formatMessage(msgItem.mContact, msgItem.mBody,
194                                             msgItem.mSubject, msgItem.mTimestamp);
195            msgItem.setCachedFormattedMessage(formattedMessage);
196        }
197        mBodyTextView.setText(formattedMessage);
198
199        if (msgItem.isSms()) {
200            hideMmsViewIfNeeded();
201        } else {
202            Presenter presenter = PresenterFactory.getPresenter(
203                    "MmsThumbnailPresenter", mContext,
204                    this, msgItem.mSlideshow);
205            presenter.present();
206
207            if (msgItem.mAttachmentType != WorkingMessage.TEXT) {
208                inflateMmsView();
209                mMmsView.setVisibility(View.VISIBLE);
210                setOnClickListener(msgItem);
211                drawPlaybackButton(msgItem);
212            } else {
213                hideMmsViewIfNeeded();
214            }
215        }
216
217        drawLeftStatusIndicator(msgItem.mBoxId);
218        drawRightStatusIndicator(msgItem);
219    }
220
221    private void hideMmsViewIfNeeded() {
222        if (mMmsView != null) {
223            mMmsView.setVisibility(View.GONE);
224        }
225    }
226
227    public void startAudio() {
228        // TODO Auto-generated method stub
229    }
230
231    public void startVideo() {
232        // TODO Auto-generated method stub
233    }
234
235    public void setAudio(Uri audio, String name, Map<String, ?> extras) {
236        // TODO Auto-generated method stub
237    }
238
239    public void setImage(String name, Bitmap bitmap) {
240        inflateMmsView();
241
242        if (null == bitmap) {
243            bitmap = BitmapFactory.decodeResource(getResources(),
244                    R.drawable.ic_missing_thumbnail_picture);
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) {
272        CharSequence template = mContext.getResources().getText(R.string.name_colon);
273        SpannableStringBuilder buf =
274            new SpannableStringBuilder(TextUtils.replace(template,
275                new String[] { "%s" },
276                new CharSequence[] { contact }));
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 WorkingMessage.SLIDESHOW:
315            case WorkingMessage.AUDIO:
316            case WorkingMessage.VIDEO:
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 WorkingMessage.VIDEO:
335            case WorkingMessage.AUDIO:
336            case WorkingMessage.SLIDESHOW:
337                MessageUtils.viewMmsMessageAttachment(mContext, mi.mMessageUri, mi.mSlideshow);
338                break;
339        }
340    }
341
342    public void onMessageListItemClick() {
343        URLSpan[] spans = mBodyTextView.getUrls();
344
345        if (spans.length == 0) {
346            // Do nothing.
347        } else if (spans.length == 1) {
348            Uri uri = Uri.parse(spans[0].getURL());
349            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
350            intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
351
352            mContext.startActivity(intent);
353        } else {
354            final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans);
355
356            ArrayAdapter<String> adapter =
357                new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) {
358                public View getView(int position, View convertView, ViewGroup parent) {
359                    View v = super.getView(position, convertView, parent);
360                    try {
361                        String url = getItem(position).toString();
362                        TextView tv = (TextView) v;
363                        Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
364                        if (d != null) {
365                            d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight());
366                            tv.setCompoundDrawablePadding(10);
367                            tv.setCompoundDrawables(d, null, null, null);
368                        }
369                        final String telPrefix = "tel:";
370                        if (url.startsWith(telPrefix)) {
371                            url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length()));
372                        }
373                        tv.setText(url);
374                    } catch (android.content.pm.PackageManager.NameNotFoundException ex) {
375                        ;
376                    }
377                    return v;
378                }
379            };
380
381            AlertDialog.Builder b = new AlertDialog.Builder(mContext);
382
383            DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() {
384                public final void onClick(DialogInterface dialog, int which) {
385                    if (which >= 0) {
386                        Uri uri = Uri.parse(urls.get(which));
387                        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
388                        intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName());
389                        mContext.startActivity(intent);
390                    }
391                }
392            };
393
394            b.setTitle(R.string.select_link_title);
395            b.setCancelable(true);
396            b.setAdapter(adapter, click);
397
398            b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
399                public final void onClick(DialogInterface dialog, int which) {
400                    dialog.dismiss();
401                }
402            });
403
404            b.show();
405        }
406    }
407
408
409    private void setOnClickListener(final MessageItem msgItem) {
410        switch(msgItem.mAttachmentType) {
411        case WorkingMessage.IMAGE:
412        case WorkingMessage.VIDEO:
413            mImageView.setOnClickListener(new OnClickListener() {
414                public void onClick(View v) {
415                    MessageUtils.viewMmsMessageAttachment(mContext, null, msgItem.mSlideshow);
416                }
417            });
418            mImageView.setOnLongClickListener(new OnLongClickListener() {
419                public boolean onLongClick(View v) {
420                    return v.showContextMenu();
421                }
422            });
423            break;
424
425        default:
426            mImageView.setOnClickListener(null);
427            break;
428        }
429    }
430
431    private void drawLeftStatusIndicator(int msgBoxId) {
432        switch (msgBoxId) {
433            case Mms.MESSAGE_BOX_INBOX:
434                mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue);
435                break;
436
437            case Mms.MESSAGE_BOX_DRAFTS:
438            case Sms.MESSAGE_TYPE_FAILED:
439            case Sms.MESSAGE_TYPE_QUEUED:
440            case Mms.MESSAGE_BOX_OUTBOX:
441                mMsgListItem.setBackgroundResource(R.drawable.listitem_background);
442                break;
443
444            default:
445                mMsgListItem.setBackgroundResource(R.drawable.listitem_background);
446                break;
447        }
448    }
449
450    public static boolean isFailedMessage(MessageItem msgItem) {
451        boolean isFailedMms = msgItem.isMms()
452                            && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT);
453        boolean isFailedSms = msgItem.isSms()
454                            && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED);
455        return isFailedMms || isFailedSms;
456    }
457
458    private void setErrorIndicatorClickListener(final MessageItem msgItem) {
459        String type = msgItem.mType;
460        final int what;
461        if (type.equals("sms")) {
462            what = MSG_LIST_EDIT_SMS;
463        } else {
464            what = MSG_LIST_EDIT_MMS;
465        }
466        mRightStatusIndicator.setOnClickListener(new OnClickListener() {
467            public void onClick(View v) {
468                if (null != mHandler) {
469                    Message msg = Message.obtain(mHandler, what);
470                    msg.obj = new Long(msgItem.mMsgId);
471                    msg.sendToTarget();
472                }
473            }
474        });
475    }
476
477    private void drawRightStatusIndicator(MessageItem msgItem) {
478        if (msgItem.isOutgoingMessage()) {
479            if (isFailedMessage(msgItem)) {
480                mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered);
481                setErrorIndicatorClickListener(msgItem);
482            } else {
483                mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending);
484            }
485            mRightStatusIndicator.setVisibility(View.VISIBLE);
486        } else if (msgItem.mDeliveryReport || msgItem.mReadReport) {
487            mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details);
488            mRightStatusIndicator.setVisibility(View.VISIBLE);
489        } else if (msgItem.mLocked) {
490            mRightStatusIndicator.setImageResource(R.drawable.lock);
491            mRightStatusIndicator.setVisibility(View.VISIBLE);
492        } else {
493            mRightStatusIndicator.setVisibility(View.GONE);
494        }
495    }
496
497    public void setImageRegionFit(String fit) {
498        // TODO Auto-generated method stub
499    }
500
501    public void setImageVisibility(boolean visible) {
502        // TODO Auto-generated method stub
503    }
504
505    public void setText(String name, String text) {
506        // TODO Auto-generated method stub
507    }
508
509    public void setTextVisibility(boolean visible) {
510        // TODO Auto-generated method stub
511    }
512
513    public void setVideo(String name, Uri video) {
514        inflateMmsView();
515        Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video);
516        if (null == bitmap) {
517            bitmap = BitmapFactory.decodeResource(getResources(),
518                    R.drawable.ic_missing_thumbnail_video);
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