MessageListItem.java revision 72735c62aba8fd2a9420a0f9f83d22543e3c164f
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 mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject, 145 msgSizeText + "\n" + msgItem.mTimestamp)); 146 147 int state = DownloadManager.getInstance().getState(msgItem.mMessageUri); 148 switch (state) { 149 case DownloadManager.STATE_DOWNLOADING: 150 inflateDownloadControls(); 151 mDownloadingLabel.setVisibility(View.VISIBLE); 152 mDownloadButton.setVisibility(View.GONE); 153 break; 154 case DownloadManager.STATE_UNSTARTED: 155 case DownloadManager.STATE_TRANSIENT_FAILURE: 156 case DownloadManager.STATE_PERMANENT_FAILURE: 157 default: 158 setLongClickable(true); 159 inflateDownloadControls(); 160 mDownloadingLabel.setVisibility(View.GONE); 161 mDownloadButton.setVisibility(View.VISIBLE); 162 mDownloadButton.setOnClickListener(new OnClickListener() { 163 public void onClick(View v) { 164 mDownloadingLabel.setVisibility(View.VISIBLE); 165 mDownloadButton.setVisibility(View.GONE); 166 Intent intent = new Intent(mContext, TransactionService.class); 167 intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString()); 168 intent.putExtra(TransactionBundle.TRANSACTION_TYPE, 169 Transaction.RETRIEVE_TRANSACTION); 170 mContext.startService(intent); 171 } 172 }); 173 break; 174 } 175 176 // Hide the error indicator. 177 mRightStatusIndicator.setVisibility(View.GONE); 178 179 drawLeftStatusIndicator(msgItem.mBoxId); 180 } 181 182 private void bindCommonMessage(final MessageItem msgItem) { 183 if (mDownloadButton != null) { 184 mDownloadButton.setVisibility(View.GONE); 185 mDownloadingLabel.setVisibility(View.GONE); 186 } 187 // Since the message text should be concatenated with the sender's 188 // address(or name), I have to display it here instead of 189 // displaying it by the Presenter. 190 mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 191 192 // Get and/or lazily set the formatted message from/on the 193 // MessageItem. Because the MessageItem instances come from a 194 // cache (currently of size ~50), the hit rate on avoiding the 195 // expensive formatMessage() call is very high. 196 CharSequence formattedMessage = msgItem.getCachedFormattedMessage(); 197 if (formattedMessage == null) { 198 formattedMessage = formatMessage(msgItem.mContact, msgItem.mBody, 199 msgItem.mSubject, msgItem.mTimestamp); 200 msgItem.setCachedFormattedMessage(formattedMessage); 201 } 202 mBodyTextView.setText(formattedMessage); 203 204 if (msgItem.isSms()) { 205 hideMmsViewIfNeeded(); 206 } else { 207 Presenter presenter = PresenterFactory.getPresenter( 208 "MmsThumbnailPresenter", mContext, 209 this, msgItem.mSlideshow); 210 presenter.present(); 211 212 if (msgItem.mAttachmentType != AttachmentEditor.TEXT_ONLY) { 213 inflateMmsView(); 214 mMmsView.setVisibility(View.VISIBLE); 215 setOnClickListener(msgItem); 216 drawPlaybackButton(msgItem); 217 } else { 218 hideMmsViewIfNeeded(); 219 } 220 } 221 222 drawLeftStatusIndicator(msgItem.mBoxId); 223 drawRightStatusIndicator(msgItem); 224 } 225 226 private void hideMmsViewIfNeeded() { 227 if (mMmsView != null) { 228 mMmsView.setVisibility(View.GONE); 229 } 230 } 231 232 public void startAudio() { 233 // TODO Auto-generated method stub 234 } 235 236 public void startVideo() { 237 // TODO Auto-generated method stub 238 } 239 240 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 241 // TODO Auto-generated method stub 242 } 243 244 public void setImage(String name, Bitmap bitmap) { 245 inflateMmsView(); 246 247 mImageView.setImageBitmap(bitmap); 248 mImageView.setVisibility(VISIBLE); 249 } 250 251 private void inflateMmsView() { 252 if (mMmsView == null) { 253 //inflate the surrounding view_stub 254 findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE); 255 256 mMmsView = findViewById(R.id.mms_view); 257 mImageView = (ImageView) findViewById(R.id.image_view); 258 mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); 259 } 260 } 261 262 private void inflateDownloadControls() { 263 if (mDownloadButton == null) { 264 //inflate the download controls 265 findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); 266 mDownloadButton = (Button) findViewById(R.id.btn_download_msg); 267 mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); 268 } 269 } 270 271 private CharSequence formatMessage(String contact, String body, String subject, 272 String timestamp) { 273 SpannableStringBuilder buf = new SpannableStringBuilder(contact); 274 buf.append(": "); 275 buf.setSpan(STYLE_BOLD, 0, buf.length(), 276 Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 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 AttachmentEditor.SLIDESHOW_ATTACHMENT: 315 case AttachmentEditor.AUDIO_ATTACHMENT: 316 case AttachmentEditor.VIDEO_ATTACHMENT: 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 AttachmentEditor.VIDEO_ATTACHMENT: 335 MessageUtils.viewSimpleSlideshow(mContext, mi.mSlideshow); 336 break; 337 case AttachmentEditor.AUDIO_ATTACHMENT: 338 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 339 Intent intent = new Intent( 340 mContext, SlideshowActivity.class); 341 intent.setData(mi.mMessageUri); 342 mContext.startActivity(intent); 343 break; 344 } 345 } 346 347 public void onMessageListItemClick() { 348 URLSpan[] spans = mBodyTextView.getUrls(); 349 350 if (spans.length == 0) { 351 // Do nothing. 352 } else if (spans.length == 1) { 353 Uri uri = Uri.parse(spans[0].getURL()); 354 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 355 356 mContext.startActivity(intent); 357 } else { 358 final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); 359 360 ArrayAdapter<String> adapter = 361 new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) { 362 public View getView(int position, View convertView, ViewGroup parent) { 363 View v = super.getView(position, convertView, parent); 364 try { 365 String url = getItem(position).toString(); 366 TextView tv = (TextView) v; 367 Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 368 if (d != null) { 369 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 370 tv.setCompoundDrawablePadding(10); 371 tv.setCompoundDrawables(d, null, null, null); 372 } 373 final String telPrefix = "tel:"; 374 if (url.startsWith(telPrefix)) { 375 url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length())); 376 } 377 tv.setText(url); 378 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 379 ; 380 } 381 return v; 382 } 383 }; 384 385 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 386 387 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 388 public final void onClick(DialogInterface dialog, int which) { 389 if (which >= 0) { 390 Uri uri = Uri.parse(urls.get(which)); 391 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 392 intent.addCategory(Intent.CATEGORY_BROWSABLE); 393 mContext.startActivity(intent); 394 } 395 } 396 }; 397 398 b.setTitle(R.string.select_link_title); 399 b.setCancelable(true); 400 b.setAdapter(adapter, click); 401 402 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 403 public final void onClick(DialogInterface dialog, int which) { 404 dialog.dismiss(); 405 } 406 }); 407 408 b.show(); 409 } 410 } 411 412 413 private void setOnClickListener(final MessageItem msgItem) { 414 switch(msgItem.mAttachmentType) { 415 case AttachmentEditor.IMAGE_ATTACHMENT: 416 case AttachmentEditor.VIDEO_ATTACHMENT: 417 mImageView.setOnClickListener(new OnClickListener() { 418 public void onClick(View v) { 419 MessageUtils.viewSimpleSlideshow(mContext, msgItem.mSlideshow); 420 } 421 }); 422 mImageView.setOnLongClickListener(new OnLongClickListener() { 423 public boolean onLongClick(View v) { 424 return v.showContextMenu(); 425 } 426 }); 427 break; 428 429 default: 430 mImageView.setOnClickListener(null); 431 break; 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