MessageListItem.java revision 153ae99e0a7d626a24d61475eeb133249deb448c
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; 28 29import android.app.AlertDialog; 30import android.content.Context; 31import android.content.DialogInterface; 32import android.content.Intent; 33import android.graphics.Bitmap; 34import android.graphics.BitmapFactory; 35import android.graphics.Canvas; 36import android.graphics.Paint; 37import android.graphics.Typeface; 38import android.graphics.drawable.Drawable; 39import android.media.MediaMetadataRetriever; 40import android.media.MediaPlayer; 41import android.net.Uri; 42import android.os.Handler; 43import android.os.Message; 44import android.provider.Telephony.Mms; 45import android.provider.Telephony.MmsSms; 46import android.provider.Telephony.Sms; 47import android.telephony.PhoneNumberUtils; 48import android.text.Spannable; 49import android.text.SpannableStringBuilder; 50import android.text.TextUtils; 51import android.text.method.HideReturnsTransformationMethod; 52import android.text.style.AbsoluteSizeSpan; 53import android.text.style.BackgroundColorSpan; 54import android.text.style.ForegroundColorSpan; 55import android.text.style.LineBackgroundSpan; 56import android.text.style.StyleSpan; 57import android.text.style.URLSpan; 58import android.util.AttributeSet; 59import android.util.Log; 60import android.view.View; 61import android.view.ViewGroup; 62import android.view.View.OnClickListener; 63import android.widget.ArrayAdapter; 64import android.widget.Button; 65import android.widget.ImageButton; 66import android.widget.ImageView; 67import android.widget.LinearLayout; 68import android.widget.TextView; 69 70import java.io.IOException; 71import java.util.Map; 72 73/** 74 * This class provides view of a message in the messages list. 75 */ 76public class MessageListItem extends LinearLayout implements 77 SlideViewInterface, OnClickListener { 78 public static final String EXTRA_URLS = "com.android.mms.ExtraUrls"; 79 80 private static final String TAG = "MessageListItem"; 81 private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD); 82 83 static final int MSG_LIST_EDIT_MMS = 1; 84 static final int MSG_LIST_EDIT_SMS = 2; 85 86 private View mMsgListItem; 87 private View mMmsView; 88 private ImageView mImageView; 89 private ImageView mRightStatusIndicator; 90 private ImageButton mSlideShowButton; 91 private TextView mBodyTextView; 92 private Button mDownloadButton; 93 private TextView mDownloadingLabel; 94 private Handler mHandler; 95 private MessageItem mMessageItem; 96 97 public MessageListItem(Context context) { 98 super(context); 99 } 100 101 public MessageListItem(Context context, AttributeSet attrs) { 102 super(context, attrs); 103 } 104 105 @Override 106 protected void onFinishInflate() { 107 super.onFinishInflate(); 108 109 mMsgListItem = findViewById(R.id.msg_list_item); 110 mBodyTextView = (TextView) findViewById(R.id.text_view); 111 mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator); 112 } 113 114 public void bind(MessageItem msgItem) { 115 mMessageItem = msgItem; 116 117 setLongClickable(false); 118 119 switch (msgItem.mMessageType) { 120 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 121 bindNotifInd(msgItem); 122 break; 123 default: 124 bindCommonMessage(msgItem); 125 break; 126 } 127 } 128 129 public MessageItem getMessageItem() { 130 return mMessageItem; 131 } 132 133 public void setMsgListItemHandler(Handler handler) { 134 mHandler = handler; 135 } 136 137 private void bindNotifInd(final MessageItem msgItem) { 138 hideMmsViewIfNeeded(); 139 140 String msgSizeText = mContext.getString(R.string.message_size_label) 141 + String.valueOf((msgItem.mMessageSize + 1023) / 1024) 142 + mContext.getString(R.string.kilobyte); 143 144 boolean drawWithBackground = msgItem.mBoxId == Mms.MESSAGE_BOX_INBOX; 145 146 mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject, 147 msgSizeText + "\n" + msgItem.mTimestamp, 148 drawWithBackground)); 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 boolean drawWithBackground = msgItem.mBoxId == Mms.MESSAGE_BOX_INBOX; 196 197 mBodyTextView.setText(formatMessage(msgItem.mContact, msgItem.mBody, 198 msgItem.mSubject, msgItem.mTimestamp, 199 drawWithBackground)); 200 // TODO part of changing contact names to links 201 //mBodyTextView.setText(formatMessage(msgItem.mAddress, msgItem.mBody)); 202 203 if (msgItem.isSms()) { 204 hideMmsViewIfNeeded(); 205 } else { 206 Presenter presenter = PresenterFactory.getPresenter( 207 "MmsThumbnailPresenter", mContext, 208 this, msgItem.mSlideshow); 209 presenter.present(); 210 211 if (msgItem.mAttachmentType != AttachmentEditor.TEXT_ONLY) { 212 inflateMmsView(); 213 mMmsView.setVisibility(View.VISIBLE); 214 setOnClickListener(msgItem); 215 drawPlaybackButton(msgItem); 216 } else { 217 hideMmsViewIfNeeded(); 218 } 219 } 220 221 drawLeftStatusIndicator(msgItem.mBoxId); 222 drawRightStatusIndicator(msgItem); 223 } 224 225 private void hideMmsViewIfNeeded() { 226 if (mMmsView != null) { 227 mMmsView.setVisibility(View.GONE); 228 } 229 } 230 231 public void startAudio() { 232 // TODO Auto-generated method stub 233 } 234 235 public void startVideo() { 236 // TODO Auto-generated method stub 237 } 238 239 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 240 // TODO Auto-generated method stub 241 } 242 243 public void setImage(String name, Bitmap bitmap) { 244 inflateMmsView(); 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, boolean drawBackground) { 272 SpannableStringBuilder buf = new SpannableStringBuilder(contact); 273 buf.append(": "); 274 buf.setSpan(STYLE_BOLD, 0, buf.length(), 275 Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 276 277 boolean hasSubject = !TextUtils.isEmpty(subject); 278 if (hasSubject) { 279 buf.append(mContext.getResources().getString(R.string.inline_subject, subject)); 280 } 281 282 if (!TextUtils.isEmpty(body)) { 283 if (hasSubject) { 284 buf.append(" - "); 285 } 286 SmileyParser parser = SmileyParser.getInstance(); 287 buf.append(parser.addSmileySpans(body)); 288 } 289 290 buf.append("\n"); 291 int startOffset = buf.length(); 292 293 // put a one pixel high spacer line between the message and the time stamp as requested 294 // by the spec. 295 buf.append("\n"); 296 buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(), 297 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 298 299 startOffset = buf.length(); 300 buf.append(timestamp); 301 buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(), 302 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 303 // Make the timestamp text not as dark 304 int color = mContext.getResources().getColor(R.color.timestamp_color); 305 buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(), 306 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 307 308 return buf; 309 } 310 311 private void drawPlaybackButton(MessageItem msgItem) { 312 switch (msgItem.mAttachmentType) { 313 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 314 case AttachmentEditor.AUDIO_ATTACHMENT: 315 case AttachmentEditor.VIDEO_ATTACHMENT: 316 // Show the 'Play' button and bind message info on it. 317 mSlideShowButton.setTag(msgItem); 318 // Set call-back for the 'Play' button. 319 mSlideShowButton.setOnClickListener(this); 320 mSlideShowButton.setVisibility(View.VISIBLE); 321 setLongClickable(true); 322 break; 323 default: 324 mSlideShowButton.setVisibility(View.GONE); 325 break; 326 } 327 } 328 329 // OnClick Listener for the playback button 330 public void onClick(View v) { 331 MessageItem mi = (MessageItem) v.getTag(); 332 switch (mi.mAttachmentType) { 333 case AttachmentEditor.AUDIO_ATTACHMENT: 334 case AttachmentEditor.VIDEO_ATTACHMENT: 335 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 336 Intent intent = new Intent( 337 mContext, SlideshowActivity.class); 338 intent.setData(mi.mMessageUri); 339 mContext.startActivity(intent); 340 break; 341 } 342 } 343 344 public void onMessageListItemClick() { 345 URLSpan[] spans = mBodyTextView.getUrls(); 346 347 if (spans.length == 0) { 348 // Do nothing. 349 } else if (spans.length == 1) { 350 Uri uri = Uri.parse(spans[0].getURL()); 351 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 352 353 mContext.startActivity(intent); 354 } else { 355 final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); 356 357 ArrayAdapter<String> adapter = 358 new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) { 359 public View getView(int position, View convertView, ViewGroup parent) { 360 View v = super.getView(position, convertView, parent); 361 try { 362 String url = getItem(position).toString(); 363 TextView tv = (TextView) v; 364 Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 365 if (d != null) { 366 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 367 tv.setCompoundDrawablePadding(10); 368 tv.setCompoundDrawables(d, null, null, null); 369 } 370 final String telPrefix = "tel:"; 371 if (url.startsWith(telPrefix)) { 372 url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length())); 373 } 374 tv.setText(url); 375 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 376 ; 377 } 378 return v; 379 } 380 }; 381 382 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 383 384 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 385 public final void onClick(DialogInterface dialog, int which) { 386 if (which >= 0) { 387 Uri uri = Uri.parse(urls.get(which)); 388 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 389 intent.addCategory(Intent.CATEGORY_BROWSABLE); 390 mContext.startActivity(intent); 391 } 392 } 393 }; 394 395 b.setTitle(R.string.select_link_title); 396 b.setCancelable(true); 397 b.setAdapter(adapter, click); 398 399 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 400 public final void onClick(DialogInterface dialog, int which) { 401 dialog.dismiss(); 402 } 403 }); 404 405 b.show(); 406 } 407 } 408 409 410 private void setOnClickListener(final MessageItem msgItem) { 411 if (msgItem.mAttachmentType == AttachmentEditor.IMAGE_ATTACHMENT) { 412 mImageView.setOnClickListener(new OnClickListener() { 413 public void onClick(View v) { 414 // FIXME: Use SlideshowActivity to view image for the time being. 415 // As described in UI spec, pressing an inline attachment will 416 // open up the full view of the attachment in its associated app 417 // (here should the pictures app). 418 // But the <ViewImage> would only show images in MediaStore. 419 // Should we save a copy to MediaStore temporarily for displaying? 420 Intent intent = new Intent(mContext, SlideshowActivity.class); 421 intent.setData(msgItem.mMessageUri); 422 mContext.startActivity(intent); 423 } 424 }); 425 mImageView.setOnLongClickListener(new OnLongClickListener() { 426 public boolean onLongClick(View v) { 427 return v.showContextMenu(); 428 } 429 }); 430 } else { 431 mImageView.setOnClickListener(null); 432 } 433 } 434 435 private void drawLeftStatusIndicator(int msgBoxId) { 436 switch (msgBoxId) { 437 case Mms.MESSAGE_BOX_INBOX: 438 mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue); 439 break; 440 441 case Mms.MESSAGE_BOX_DRAFTS: 442 case Sms.MESSAGE_TYPE_FAILED: 443 case Sms.MESSAGE_TYPE_QUEUED: 444 case Mms.MESSAGE_BOX_OUTBOX: 445 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 446 break; 447 448 default: 449 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 450 break; 451 } 452 } 453 454 public static boolean isFailedMessage(MessageItem msgItem) { 455 boolean isFailedMms = msgItem.isMms() 456 && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT); 457 boolean isFailedSms = msgItem.isSms() 458 && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED); 459 return isFailedMms || isFailedSms; 460 } 461 462 private void setErrorIndicatorClickListener(final MessageItem msgItem) { 463 String type = msgItem.mType; 464 final int what; 465 if (type.equals("sms")) { 466 what = MSG_LIST_EDIT_SMS; 467 } else { 468 what = MSG_LIST_EDIT_MMS; 469 } 470 mRightStatusIndicator.setOnClickListener(new OnClickListener() { 471 public void onClick(View v) { 472 if (null != mHandler) { 473 Message msg = Message.obtain(mHandler, what); 474 msg.obj = new Long(msgItem.mMsgId); 475 msg.sendToTarget(); 476 } 477 } 478 }); 479 } 480 481 private void drawRightStatusIndicator(MessageItem msgItem) { 482 if (msgItem.isOutgoingMessage()) { 483 if (isFailedMessage(msgItem)) { 484 mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); 485 setErrorIndicatorClickListener(msgItem); 486 } else { 487 mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending); 488 } 489 mRightStatusIndicator.setVisibility(View.VISIBLE); 490 } else if (msgItem.mDeliveryReport || msgItem.mReadReport) { 491 mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details); 492 mRightStatusIndicator.setVisibility(View.VISIBLE); 493 } else { 494 mRightStatusIndicator.setVisibility(View.GONE); 495 } 496 } 497 498 public void setImageRegionFit(String fit) { 499 // TODO Auto-generated method stub 500 } 501 502 public void setImageVisibility(boolean visible) { 503 // TODO Auto-generated method stub 504 } 505 506 public void setText(String name, String text) { 507 // TODO Auto-generated method stub 508 } 509 510 public void setTextVisibility(boolean visible) { 511 // TODO Auto-generated method stub 512 } 513 514 public void setVideo(String name, Uri video) { 515 inflateMmsView(); 516 Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video); 517 if (null == bitmap) { 518 bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mms_play_btn); 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