MessageListItem.java revision 8eed706474910ccb978acda03e85d3261037da6e
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.google.android.mms.pdu.PduHeaders;
26
27import android.content.Context;
28import android.content.Intent;
29import android.graphics.Bitmap;
30import android.graphics.Typeface;
31import android.media.MediaPlayer;
32import android.net.Uri;
33import android.os.Handler;
34import android.os.Message;
35import android.provider.Telephony.Mms;
36import android.provider.Telephony.MmsSms;
37import android.provider.Telephony.Sms;
38import android.text.Spannable;
39import android.text.SpannableStringBuilder;
40import android.text.TextUtils;
41import android.text.method.HideReturnsTransformationMethod;
42import android.text.style.StyleSpan;
43import android.text.style.URLSpan;
44import android.util.AttributeSet;
45import android.util.Log;
46import android.view.View;
47import android.view.View.OnClickListener;
48import android.widget.Button;
49import android.widget.ImageButton;
50import android.widget.ImageView;
51import android.widget.LinearLayout;
52import android.widget.TextView;
53
54import java.io.IOException;
55import java.util.ArrayList;
56import java.util.Map;
57
58/**
59 * This class provides view of a message in the messages list.
60 */
61public class MessageListItem extends LinearLayout implements
62        SlideViewInterface, OnClickListener {
63    public static final String EXTRA_URLS = "com.android.mms.ExtraUrls";
64
65    private static final String TAG = "MessageListItem";
66    private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD);
67
68    static final int MSG_LIST_EDIT_MMS   = 1;
69    static final int MSG_LIST_EDIT_SMS   = 2;
70
71    private View mMsgListItem;
72    private View mMmsView;
73    private ImageView mImageView;
74    private ImageView mLeftStatusIndicator;
75    private ImageView mRightStatusIndicator;
76    private ImageButton mSlideShowButton;
77    private TextView mBodyTextView;
78    private Button mDownloadButton;
79    private TextView mDownloadingLabel;
80    private Handler mHandler;
81
82    public MessageListItem(Context context) {
83        super(context);
84    }
85
86    public MessageListItem(Context context, AttributeSet attrs) {
87        super(context, attrs);
88    }
89
90    @Override
91    protected void onFinishInflate() {
92        super.onFinishInflate();
93
94        mMsgListItem = findViewById(R.id.msg_list_item);
95        mLeftStatusIndicator = (ImageView) findViewById(R.id.left_status_indicator);
96        mBodyTextView = (TextView) findViewById(R.id.text_view);
97        mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator);
98        mDownloadButton = (Button) findViewById(R.id.btn_download_msg);
99        mDownloadingLabel = (TextView) findViewById(R.id.label_downloading);
100    }
101
102    public void bind(MessageItem msgItem) {
103        setLongClickable(false);
104
105        switch (msgItem.mMessageType) {
106            case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND:
107                bindNotifInd(msgItem);
108                break;
109            default:
110                bindCommonMessage(msgItem);
111                break;
112        }
113    }
114
115    public void setMsgListItemHandler(Handler handler) {
116        mHandler = handler;
117    }
118
119    private void bindNotifInd(final MessageItem msgItem) {
120        hideMmsViewIfNeeded();
121
122        String msgSizeText = mContext.getString(R.string.message_size_label)
123                                + String.valueOf((msgItem.mMessageSize + 1023) / 1024)
124                                + mContext.getString(R.string.kilobyte);
125
126        mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject,
127                                            msgSizeText + "\n" + msgItem.mTimestamp));
128
129        int state = DownloadManager.getInstance().getState(msgItem.mMessageUri);
130        switch (state) {
131            case DownloadManager.STATE_DOWNLOADING:
132                mDownloadingLabel.setVisibility(View.VISIBLE);
133                mDownloadButton.setVisibility(View.GONE);
134                break;
135            case DownloadManager.STATE_UNSTARTED:
136            case DownloadManager.STATE_TRANSIENT_FAILURE:
137            case DownloadManager.STATE_PERMANENT_FAILURE:
138            default:
139                setLongClickable(true);
140                mDownloadingLabel.setVisibility(View.GONE);
141                mDownloadButton.setVisibility(View.VISIBLE);
142                mDownloadButton.setOnClickListener(new OnClickListener() {
143                    public void onClick(View v) {
144                        mDownloadingLabel.setVisibility(View.VISIBLE);
145                        mDownloadButton.setVisibility(View.GONE);
146                        Intent intent = new Intent(mContext, TransactionService.class);
147                        intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString());
148                        intent.putExtra(TransactionBundle.TRANSACTION_TYPE,
149                                Transaction.RETRIEVE_TRANSACTION);
150                        mContext.startService(intent);
151                    }
152                });
153                break;
154        }
155
156        // Hide the error indicator.
157        mRightStatusIndicator.setVisibility(View.GONE);
158
159        drawLeftStatusIndicator(msgItem.mBoxId);
160    }
161
162    private void bindCommonMessage(final MessageItem msgItem) {
163        mDownloadButton.setVisibility(View.GONE);
164        mDownloadingLabel.setVisibility(View.GONE);
165
166        // Since the message text should be concatenated with the sender's
167        // address(or name), I have to display it here instead of
168        // displaying it by the Presenter.
169        mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
170
171        mBodyTextView.setText(formatMessage(msgItem.mContact, msgItem.mBody,
172                                            msgItem.mSubject, msgItem.mTimestamp));
173        // TODO part of changing contact names to links
174        //mBodyTextView.setText(formatMessage(msgItem.mAddress, msgItem.mBody));
175
176        if (msgItem.isSms()) {
177            hideMmsViewIfNeeded();
178        } else {
179            Presenter presenter = PresenterFactory.getPresenter(
180                    "MmsThumbnailPresenter", mContext,
181                    this, msgItem.mSlideshow);
182            presenter.present();
183
184            if (msgItem.mAttachmentType != AttachmentEditor.TEXT_ONLY) {
185                inflateMmsView();
186                mMmsView.setVisibility(View.VISIBLE);
187                setOnClickListener(msgItem);
188                drawPlaybackButton(msgItem);
189            } else {
190                hideMmsViewIfNeeded();
191            }
192        }
193
194        drawLeftStatusIndicator(msgItem.mBoxId);
195        drawRightStatusIndicator(msgItem);
196    }
197
198    private void hideMmsViewIfNeeded() {
199        if (mMmsView != null) {
200            mMmsView.setVisibility(View.GONE);
201        }
202    }
203
204    public void startAudio() {
205        // TODO Auto-generated method stub
206    }
207
208    public void startVideo() {
209        // TODO Auto-generated method stub
210    }
211
212    public void setAudio(Uri audio, String name, Map<String, ?> extras) {
213        // TODO Auto-generated method stub
214    }
215
216    public void setImage(String name, Bitmap bitmap) {
217        inflateMmsView();
218
219        mImageView.setImageBitmap(bitmap);
220        mImageView.setVisibility(VISIBLE);
221    }
222
223    private void inflateMmsView() {
224        if (mMmsView == null) {
225            //inflate the surrounding view_stub
226            findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE);
227
228            mMmsView = findViewById(R.id.mms_view);
229            mImageView = (ImageView) findViewById(R.id.image_view);
230            mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button);
231        }
232    }
233
234    private CharSequence formatMessage(String contact, String body, String subject,
235                                       String timestamp) {
236        SpannableStringBuilder buf = new SpannableStringBuilder(contact);
237        buf.append(": ");
238        buf.setSpan(STYLE_BOLD, 0, buf.length(),
239                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
240
241        boolean hasSubject = !TextUtils.isEmpty(subject);
242        if (hasSubject) {
243            buf.append(mContext.getResources().getString(R.string.inline_subject, subject));
244        }
245
246        if (!TextUtils.isEmpty(body)) {
247            if (hasSubject) {
248                buf.append(" - ");
249            }
250            buf.append(body);
251        }
252
253        buf.append("\n");
254        buf.append(timestamp);
255
256        return buf;
257    }
258
259    private void drawPlaybackButton(MessageItem msgItem) {
260        switch (msgItem.mAttachmentType) {
261            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
262            case AttachmentEditor.AUDIO_ATTACHMENT:
263            case AttachmentEditor.VIDEO_ATTACHMENT:
264                // Show the 'Play' button and bind message info on it.
265                mSlideShowButton.setTag(msgItem);
266                // Set call-back for the 'Play' button.
267                mSlideShowButton.setOnClickListener(this);
268                mSlideShowButton.setVisibility(View.VISIBLE);
269                setLongClickable(true);
270                break;
271            default:
272                mSlideShowButton.setVisibility(View.GONE);
273                break;
274        }
275    }
276
277    // OnClick Listener for the playback button
278    public void onClick(View v) {
279        MessageItem mi = (MessageItem) v.getTag();
280        switch (mi.mAttachmentType) {
281            case AttachmentEditor.AUDIO_ATTACHMENT:
282            case AttachmentEditor.VIDEO_ATTACHMENT:
283            case AttachmentEditor.SLIDESHOW_ATTACHMENT:
284                Intent intent = new Intent(
285                        mContext, SlideshowActivity.class);
286                intent.setData(mi.mMessageUri);
287                mContext.startActivity(intent);
288                break;
289        }
290    }
291
292    public void onMessageListItemClick() {
293        URLSpan[] spans = mBodyTextView.getUrls();
294
295        if (spans.length == 0) {
296            // Do nothing.
297        } else if (spans.length == 1) {
298            Uri uri = Uri.parse(spans[0].getURL());
299            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
300
301            mContext.startActivity(intent);
302        } else {
303            Intent intent = new Intent(
304                    mContext, UrlListActivity.class);
305
306            intent.putStringArrayListExtra(
307                    EXTRA_URLS, extractUris(spans));
308            mContext.startActivity(intent);
309        }
310    }
311
312
313    private void setOnClickListener(final MessageItem msgItem) {
314        if (msgItem.mAttachmentType == AttachmentEditor.IMAGE_ATTACHMENT) {
315            mImageView.setOnClickListener(new OnClickListener() {
316                public void onClick(View v) {
317                    // FIXME: Use SlideshowActivity to view image for the time being.
318                    // As described in UI spec, pressing an inline attachment will
319                    // open up the full view of the attachment in its associated app
320                    // (here should the pictures app).
321                    // But the <ViewImage> would only show images in MediaStore.
322                    // Should we save a copy to MediaStore temporarily for displaying?
323                    Intent intent = new Intent(mContext, SlideshowActivity.class);
324                    intent.setData(msgItem.mMessageUri);
325                    mContext.startActivity(intent);
326                }
327            });
328        } else {
329            mImageView.setOnClickListener(null);
330        }
331    }
332
333    private ArrayList<String> extractUris(URLSpan[] spans) {
334        int size = spans.length;
335        ArrayList<String> accumulator = new ArrayList<String>();
336
337        for (int i = 0; i < size; i++) {
338            accumulator.add(spans[i].getURL());
339        }
340        return accumulator;
341    }
342
343    private void drawLeftStatusIndicator(int msgBoxId) {
344        switch (msgBoxId) {
345            case Mms.MESSAGE_BOX_INBOX:
346                mLeftStatusIndicator.setVisibility(VISIBLE);
347                mLeftStatusIndicator.setImageResource(R.drawable.textfield_im_left_indicator_blue);
348                mMsgListItem.setBackgroundResource(R.drawable.light_blue_background);
349                break;
350
351            case Mms.MESSAGE_BOX_DRAFTS:
352            case Sms.MESSAGE_TYPE_FAILED:
353            case Sms.MESSAGE_TYPE_QUEUED:
354            case Mms.MESSAGE_BOX_OUTBOX:
355                mLeftStatusIndicator.setVisibility(VISIBLE);
356                mLeftStatusIndicator.setImageResource(R.drawable.textfield_im_left_indicator_red);
357                mMsgListItem.setBackgroundResource(R.drawable.white_background);
358                break;
359
360            default:
361                // reserve space for one indicator to preserve alignment
362                mLeftStatusIndicator.setVisibility(INVISIBLE);
363                mMsgListItem.setBackgroundResource(R.drawable.white_background);
364                break;
365        }
366    }
367
368    private boolean isFailedMessage(MessageItem msgItem) {
369        boolean isFailedMms = msgItem.isMms()
370                            && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT);
371        boolean isFailedSms = msgItem.isSms()
372                            && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED);
373        return isFailedMms || isFailedSms;
374    }
375
376    private void setErrorIndicatorClickListener(final MessageItem msgItem) {
377        String type = msgItem.mType;
378        final int what;
379        if (type.equals("sms")) {
380            what = MSG_LIST_EDIT_SMS;
381        } else {
382            what = MSG_LIST_EDIT_MMS;
383        }
384        mRightStatusIndicator.setOnClickListener(new OnClickListener() {
385            public void onClick(View v) {
386                if (null != mHandler) {
387                    Message msg = Message.obtain(mHandler, what);
388                    msg.obj = new Long(msgItem.mMsgId);
389                    msg.sendToTarget();
390                }
391            }
392        });
393    }
394
395    private void drawRightStatusIndicator(MessageItem msgItem) {
396        if (msgItem.isOutgoingMessage()) {
397            if (isFailedMessage(msgItem)) {
398                mRightStatusIndicator.setImageResource(R.drawable.ic_sms_error);
399                setErrorIndicatorClickListener(msgItem);
400            } else {
401                mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending);
402            }
403            mRightStatusIndicator.setVisibility(View.VISIBLE);
404        } else if (msgItem.mDeliveryReport || msgItem.mReadReport) {
405            mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details);
406            mRightStatusIndicator.setVisibility(View.VISIBLE);
407        } else {
408            mRightStatusIndicator.setVisibility(View.GONE);
409        }
410    }
411
412    public void setImageRegionFit(String fit) {
413        // TODO Auto-generated method stub
414    }
415
416    public void setImageVisibility(boolean visible) {
417        // TODO Auto-generated method stub
418    }
419
420    public void setText(String name, String text) {
421        // TODO Auto-generated method stub
422    }
423
424    public void setTextVisibility(boolean visible) {
425        // TODO Auto-generated method stub
426    }
427
428    public void setVideo(String name, Uri video) {
429        inflateMmsView();
430
431        MediaPlayer mp = new MediaPlayer();
432
433        try {
434            mp.setDataSource(mContext, video);
435            mImageView.setImageBitmap(mp.getFrameAt(1000));
436            mImageView.setVisibility(VISIBLE);
437        } catch (IOException e) {
438            Log.e(TAG, "Unexpected IOException.", e);
439        } finally {
440            mp.release();
441        }
442    }
443
444    public void setVideoVisibility(boolean visible) {
445        // TODO Auto-generated method stub
446    }
447
448    public void stopAudio() {
449        // TODO Auto-generated method stub
450    }
451
452    public void stopVideo() {
453        // TODO Auto-generated method stub
454    }
455
456    public void reset() {
457        if (mImageView != null) {
458            mImageView.setVisibility(GONE);
459        }
460    }
461
462    public void setVisibility(boolean visible) {
463        // TODO Auto-generated method stub
464    }
465
466    public void pauseAudio() {
467        // TODO Auto-generated method stub
468
469    }
470
471    public void pauseVideo() {
472        // TODO Auto-generated method stub
473
474    }
475
476    public void seekAudio(int seekTo) {
477        // TODO Auto-generated method stub
478
479    }
480
481    public void seekVideo(int seekTo) {
482        // TODO Auto-generated method stub
483
484    }
485}
486