/* * Copyright (C) 2008 Esmertec AG. * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.edu.tsinghua.hpc.tmms.ui; import java.util.Map; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.provider.Browser; import android.provider.Telephony.Mms; import android.provider.Telephony.MmsSms; import android.provider.Telephony.Sms; import android.telephony.PhoneNumberUtils; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.HideReturnsTransformationMethod; import android.text.style.AbsoluteSizeSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.URLSpan; import android.util.AttributeSet; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import cn.edu.tsinghua.hpc.google.tmms.pdu.PduHeaders; import cn.edu.tsinghua.hpc.tmms.R; import cn.edu.tsinghua.hpc.tmms.data.WorkingMessage; import cn.edu.tsinghua.hpc.tmms.transaction.Transaction; import cn.edu.tsinghua.hpc.tmms.transaction.TransactionBundle; import cn.edu.tsinghua.hpc.tmms.transaction.TransactionService; import cn.edu.tsinghua.hpc.tmms.util.DownloadManager; import cn.edu.tsinghua.hpc.tmms.util.SmileyParser; import cn.edu.tsinghua.hpc.tmms.util.TIntent; /** * This class provides view of a message in the messages list. */ public class MessageListItem extends LinearLayout implements SlideViewInterface, OnClickListener { public static final String EXTRA_URLS = "cn.edu.tsinghua.hpc.tmms.ExtraUrls"; private static final String TAG = "MessageListItem"; private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD); static final int MSG_LIST_EDIT_MMS = 1; static final int MSG_LIST_EDIT_SMS = 2; private View mMsgListItem; private View mMmsView; private ImageView mImageView; private ImageView mRightStatusIndicator; private ImageButton mSlideShowButton; private TextView mBodyTextView; private Button mDownloadButton; private TextView mDownloadingLabel; private Handler mHandler; private MessageItem mMessageItem; public MessageListItem(Context context) { super(context); } public MessageListItem(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); mMsgListItem = findViewById(R.id.msg_list_item); mBodyTextView = (TextView) findViewById(R.id.text_view); mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator); } public void bind(MessageItem msgItem) { mMessageItem = msgItem; setLongClickable(false); switch (msgItem.mMessageType) { case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: bindNotifInd(msgItem); break; default: bindCommonMessage(msgItem); break; } } public MessageItem getMessageItem() { return mMessageItem; } public void setMsgListItemHandler(Handler handler) { mHandler = handler; } private void bindNotifInd(final MessageItem msgItem) { hideMmsViewIfNeeded(); String msgSizeText = this.getContext().getString(R.string.message_size_label) + String.valueOf((msgItem.mMessageSize + 1023) / 1024) + this.getContext().getString(R.string.kilobyte); mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject, msgSizeText + "\n" + msgItem.mTimestamp, msgItem.mHighlight)); int state = DownloadManager.getInstance().getState(msgItem.mMessageUri); switch (state) { case DownloadManager.STATE_DOWNLOADING: inflateDownloadControls(); mDownloadingLabel.setVisibility(View.VISIBLE); mDownloadButton.setVisibility(View.GONE); break; case DownloadManager.STATE_UNSTARTED: case DownloadManager.STATE_TRANSIENT_FAILURE: case DownloadManager.STATE_PERMANENT_FAILURE: default: setLongClickable(true); inflateDownloadControls(); mDownloadingLabel.setVisibility(View.GONE); mDownloadButton.setVisibility(View.VISIBLE); final Context mContext = this.getContext(); mDownloadButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mDownloadingLabel.setVisibility(View.VISIBLE); mDownloadButton.setVisibility(View.GONE); Intent intent = new Intent(mContext, TransactionService.class); intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString()); intent.putExtra(TransactionBundle.TRANSACTION_TYPE, Transaction.RETRIEVE_TRANSACTION); mContext.startService(intent); } }); break; } // Hide the error indicator. mRightStatusIndicator.setVisibility(View.GONE); drawLeftStatusIndicator(msgItem.mBoxId); } private void bindCommonMessage(final MessageItem msgItem) { if (mDownloadButton != null) { mDownloadButton.setVisibility(View.GONE); mDownloadingLabel.setVisibility(View.GONE); } // Since the message text should be concatenated with the sender's // address(or name), I have to display it here instead of // displaying it by the Presenter. mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); // Get and/or lazily set the formatted message from/on the // MessageItem. Because the MessageItem instances come from a // cache (currently of size ~50), the hit rate on avoiding the // expensive formatMessage() call is very high. CharSequence formattedMessage = msgItem.getCachedFormattedMessage(); if (formattedMessage == null) { formattedMessage = formatMessage(msgItem.mContact, msgItem.mBody, msgItem.mSubject, msgItem.mTimestamp, msgItem.mHighlight); msgItem.setCachedFormattedMessage(formattedMessage); } mBodyTextView.setText(formattedMessage); if (msgItem.isSms()) { hideMmsViewIfNeeded(); } else { Presenter presenter = PresenterFactory.getPresenter( "MmsThumbnailPresenter", this.getContext(), this, msgItem.mSlideshow); presenter.present(); if (msgItem.mAttachmentType != WorkingMessage.TEXT) { inflateMmsView(); mMmsView.setVisibility(View.VISIBLE); setOnClickListener(msgItem); drawPlaybackButton(msgItem); } else { hideMmsViewIfNeeded(); } } drawLeftStatusIndicator(msgItem.mBoxId); drawRightStatusIndicator(msgItem); } private void hideMmsViewIfNeeded() { if (mMmsView != null) { mMmsView.setVisibility(View.GONE); } } public void startAudio() { // TODO Auto-generated method stub } public void startVideo() { // TODO Auto-generated method stub } public void setAudio(Uri audio, String name, Map<String, ?> extras) { // TODO Auto-generated method stub } public void setImage(String name, Bitmap bitmap) { inflateMmsView(); if (null == bitmap) { bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_missing_thumbnail_picture); } mImageView.setImageBitmap(bitmap); mImageView.setVisibility(VISIBLE); } private void inflateMmsView() { if (mMmsView == null) { //inflate the surrounding view_stub findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE); mMmsView = findViewById(R.id.mms_view); mImageView = (ImageView) findViewById(R.id.image_view); mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); } } private void inflateDownloadControls() { if (mDownloadButton == null) { //inflate the download controls findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); mDownloadButton = (Button) findViewById(R.id.btn_download_msg); mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); } } private CharSequence formatMessage(String contact, String body, String subject, String timestamp, String highlight) { CharSequence template = this.getContext().getResources().getText(R.string.name_colon); SpannableStringBuilder buf = new SpannableStringBuilder(TextUtils.replace(template, new String[] { "%s" }, new CharSequence[] { contact })); boolean hasSubject = !TextUtils.isEmpty(subject); if (hasSubject) { buf.append(this.getContext().getResources().getString(R.string.inline_subject, subject)); } if (!TextUtils.isEmpty(body)) { if (hasSubject) { buf.append(" - "); } SmileyParser parser = SmileyParser.getInstance(); buf.append(parser.addSmileySpans(body)); } if (!TextUtils.isEmpty(timestamp)) { buf.append("\n"); int startOffset = buf.length(); // put a one pixel high spacer line between the message and the time stamp as requested // by the spec. buf.append("\n"); buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); startOffset = buf.length(); buf.append(timestamp); buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); // Make the timestamp text not as dark int color = this.getContext().getResources().getColor(R.color.timestamp_color); buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } if (highlight != null) { int highlightLen = highlight.length(); String s = buf.toString().toLowerCase(); int prev = 0; while (true) { int index = s.indexOf(highlight, prev); if (index == -1) { break; } buf.setSpan(new StyleSpan(Typeface.BOLD), index, index + highlightLen, 0); prev = index + highlightLen; } } return buf; } private void drawPlaybackButton(MessageItem msgItem) { switch (msgItem.mAttachmentType) { case WorkingMessage.SLIDESHOW: case WorkingMessage.AUDIO: case WorkingMessage.VIDEO: // Show the 'Play' button and bind message info on it. mSlideShowButton.setTag(msgItem); // Set call-back for the 'Play' button. mSlideShowButton.setOnClickListener(this); mSlideShowButton.setVisibility(View.VISIBLE); setLongClickable(true); // When we show the mSlideShowButton, this list item's onItemClickListener doesn't // get called. (It gets set in ComposeMessageActivity: // mMsgListView.setOnItemClickListener) Here we explicitly set the item's // onClickListener. It allows the item to respond to embedded html links and at the // same time, allows the slide show play button to work. setOnClickListener(new OnClickListener() { public void onClick(View v) { onMessageListItemClick(); } }); break; default: mSlideShowButton.setVisibility(View.GONE); break; } } // OnClick Listener for the playback button public void onClick(View v) { MessageItem mi = (MessageItem) v.getTag(); switch (mi.mAttachmentType) { case WorkingMessage.VIDEO: case WorkingMessage.AUDIO: case WorkingMessage.SLIDESHOW: MessageUtils.viewMmsMessageAttachment(this.getContext(), mi.mMessageUri, mi.mSlideshow); break; } } public void onMessageListItemClick() { URLSpan[] spans = mBodyTextView.getUrls(); if (spans.length == 0) { // Do nothing. } else if (spans.length == 1) { Uri uri = Uri.parse(spans[0].getURL()); Intent intent = new Intent(TIntent.getACTION_VIEW(this.getContext()), uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, this.getContext().getPackageName()); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); this.getContext().startActivity(intent); } else { final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this.getContext(), android.R.layout.select_dialog_item, urls) { public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); try { String url = getItem(position).toString(); TextView tv = (TextView) v; Drawable d = this.getContext().getPackageManager().getActivityIcon(new Intent(TIntent.getACTION_VIEW(this.getContext()), Uri.parse(url))); if (d != null) { d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); tv.setCompoundDrawablePadding(10); tv.setCompoundDrawables(d, null, null, null); } final String telPrefix = "tel:"; if (url.startsWith(telPrefix)) { url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length())); } tv.setText(url); } catch (android.content.pm.PackageManager.NameNotFoundException ex) { ; } return v; } }; AlertDialog.Builder b = new AlertDialog.Builder(this.getContext()); final Context mContext = this.getContext(); DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { public final void onClick(DialogInterface dialog, int which) { if (which >= 0) { Uri uri = Uri.parse(urls.get(which)); Intent intent = new Intent(TIntent.getACTION_VIEW(mContext), uri); intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); mContext.startActivity(intent); } } }; b.setTitle(R.string.select_link_title); b.setCancelable(true); b.setAdapter(adapter, click); b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public final void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); b.show(); } } private void setOnClickListener(final MessageItem msgItem) { final Context mContext = this.getContext(); switch(msgItem.mAttachmentType) { case WorkingMessage.IMAGE: case WorkingMessage.VIDEO: mImageView.setOnClickListener(new OnClickListener() { public void onClick(View v) { MessageUtils.viewMmsMessageAttachment(mContext, null, msgItem.mSlideshow); } }); mImageView.setOnLongClickListener(new OnLongClickListener() { public boolean onLongClick(View v) { return v.showContextMenu(); } }); break; default: mImageView.setOnClickListener(null); break; } } private void drawLeftStatusIndicator(int msgBoxId) { switch (msgBoxId) { case Mms.MESSAGE_BOX_INBOX: mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue); break; case Mms.MESSAGE_BOX_DRAFTS: case Sms.MESSAGE_TYPE_FAILED: case Sms.MESSAGE_TYPE_QUEUED: case Mms.MESSAGE_BOX_OUTBOX: mMsgListItem.setBackgroundResource(R.drawable.listitem_background); break; default: mMsgListItem.setBackgroundResource(R.drawable.listitem_background); break; } } public static boolean isFailedMessage(MessageItem msgItem) { boolean isFailedMms = msgItem.isMms() && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT); boolean isFailedSms = msgItem.isSms() && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED); return isFailedMms || isFailedSms; } private void setErrorIndicatorClickListener(final MessageItem msgItem) { String type = msgItem.mType; final int what; if (type.equals("sms")) { what = MSG_LIST_EDIT_SMS; } else { what = MSG_LIST_EDIT_MMS; } mRightStatusIndicator.setOnClickListener(new OnClickListener() { public void onClick(View v) { if (null != mHandler) { Message msg = Message.obtain(mHandler, what); msg.obj = new Long(msgItem.mMsgId); msg.sendToTarget(); } } }); } private void drawRightStatusIndicator(MessageItem msgItem) { if (msgItem.isOutgoingMessage()) { if (isFailedMessage(msgItem)) { mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); setErrorIndicatorClickListener(msgItem); } else { mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending); } mRightStatusIndicator.setVisibility(View.VISIBLE); } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.INFO || msgItem.mReadReport) { mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details); mRightStatusIndicator.setVisibility(View.VISIBLE); } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.PENDING) { mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_pending); mRightStatusIndicator.setVisibility(View.VISIBLE); } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.FAILED) { mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); mRightStatusIndicator.setVisibility(View.VISIBLE); } else if (msgItem.mDeliveryStatus == MessageItem.DeliveryStatus.RECEIVED) { mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_delivered); mRightStatusIndicator.setVisibility(View.VISIBLE); } else if (msgItem.mLocked) { mRightStatusIndicator.setImageResource(R.drawable.ic_lock_message_sms); mRightStatusIndicator.setVisibility(View.VISIBLE); } else { mRightStatusIndicator.setVisibility(View.GONE); } } public void setImageRegionFit(String fit) { // TODO Auto-generated method stub } public void setImageVisibility(boolean visible) { // TODO Auto-generated method stub } public void setText(String name, String text) { // TODO Auto-generated method stub } public void setTextVisibility(boolean visible) { // TODO Auto-generated method stub } public void setVideo(String name, Uri video) { inflateMmsView(); Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(this.getContext(), video); if (null == bitmap) { bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_missing_thumbnail_video); } mImageView.setImageBitmap(bitmap); mImageView.setVisibility(VISIBLE); } public void setVideoVisibility(boolean visible) { // TODO Auto-generated method stub } public void stopAudio() { // TODO Auto-generated method stub } public void stopVideo() { // TODO Auto-generated method stub } public void reset() { if (mImageView != null) { mImageView.setVisibility(GONE); } } public void setVisibility(boolean visible) { // TODO Auto-generated method stub } public void pauseAudio() { // TODO Auto-generated method stub } public void pauseVideo() { // TODO Auto-generated method stub } public void seekAudio(int seekTo) { // TODO Auto-generated method stub } public void seekVideo(int seekTo) { // TODO Auto-generated method stub } }