MessageListItem.java revision a1a71dd05a759715c82bfc8bcaa12bbe2d22c751
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.Browser; 45import android.provider.Telephony.Mms; 46import android.provider.Telephony.MmsSms; 47import android.provider.Telephony.Sms; 48import android.telephony.PhoneNumberUtils; 49import android.text.Spannable; 50import android.text.SpannableStringBuilder; 51import android.text.TextUtils; 52import android.text.method.HideReturnsTransformationMethod; 53import android.text.style.AbsoluteSizeSpan; 54import android.text.style.BackgroundColorSpan; 55import android.text.style.ForegroundColorSpan; 56import android.text.style.LineBackgroundSpan; 57import android.text.style.StyleSpan; 58import android.text.style.URLSpan; 59import android.util.AttributeSet; 60import android.util.Log; 61import android.view.View; 62import android.view.ViewGroup; 63import android.view.View.OnClickListener; 64import android.widget.ArrayAdapter; 65import android.widget.Button; 66import android.widget.ImageButton; 67import android.widget.ImageView; 68import android.widget.LinearLayout; 69import android.widget.TextView; 70 71import java.io.IOException; 72import java.util.Map; 73 74/** 75 * This class provides view of a message in the messages list. 76 */ 77public class MessageListItem extends LinearLayout implements 78 SlideViewInterface, OnClickListener { 79 public static final String EXTRA_URLS = "com.android.mms.ExtraUrls"; 80 81 private static final String TAG = "MessageListItem"; 82 private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD); 83 84 static final int MSG_LIST_EDIT_MMS = 1; 85 static final int MSG_LIST_EDIT_SMS = 2; 86 87 private View mMsgListItem; 88 private View mMmsView; 89 private ImageView mImageView; 90 private ImageView mRightStatusIndicator; 91 private ImageButton mSlideShowButton; 92 private TextView mBodyTextView; 93 private Button mDownloadButton; 94 private TextView mDownloadingLabel; 95 private Handler mHandler; 96 private MessageItem mMessageItem; 97 98 public MessageListItem(Context context) { 99 super(context); 100 } 101 102 public MessageListItem(Context context, AttributeSet attrs) { 103 super(context, attrs); 104 } 105 106 @Override 107 protected void onFinishInflate() { 108 super.onFinishInflate(); 109 110 mMsgListItem = findViewById(R.id.msg_list_item); 111 mBodyTextView = (TextView) findViewById(R.id.text_view); 112 mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator); 113 } 114 115 public void bind(MessageItem msgItem) { 116 mMessageItem = msgItem; 117 118 setLongClickable(false); 119 120 switch (msgItem.mMessageType) { 121 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 122 bindNotifInd(msgItem); 123 break; 124 default: 125 bindCommonMessage(msgItem); 126 break; 127 } 128 } 129 130 public MessageItem getMessageItem() { 131 return mMessageItem; 132 } 133 134 public void setMsgListItemHandler(Handler handler) { 135 mHandler = handler; 136 } 137 138 private void bindNotifInd(final MessageItem msgItem) { 139 hideMmsViewIfNeeded(); 140 141 String msgSizeText = mContext.getString(R.string.message_size_label) 142 + String.valueOf((msgItem.mMessageSize + 1023) / 1024) 143 + mContext.getString(R.string.kilobyte); 144 145 mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject, 146 msgSizeText + "\n" + msgItem.mTimestamp)); 147 148 int state = DownloadManager.getInstance().getState(msgItem.mMessageUri); 149 switch (state) { 150 case DownloadManager.STATE_DOWNLOADING: 151 inflateDownloadControls(); 152 mDownloadingLabel.setVisibility(View.VISIBLE); 153 mDownloadButton.setVisibility(View.GONE); 154 break; 155 case DownloadManager.STATE_UNSTARTED: 156 case DownloadManager.STATE_TRANSIENT_FAILURE: 157 case DownloadManager.STATE_PERMANENT_FAILURE: 158 default: 159 setLongClickable(true); 160 inflateDownloadControls(); 161 mDownloadingLabel.setVisibility(View.GONE); 162 mDownloadButton.setVisibility(View.VISIBLE); 163 mDownloadButton.setOnClickListener(new OnClickListener() { 164 public void onClick(View v) { 165 mDownloadingLabel.setVisibility(View.VISIBLE); 166 mDownloadButton.setVisibility(View.GONE); 167 Intent intent = new Intent(mContext, TransactionService.class); 168 intent.putExtra(TransactionBundle.URI, msgItem.mMessageUri.toString()); 169 intent.putExtra(TransactionBundle.TRANSACTION_TYPE, 170 Transaction.RETRIEVE_TRANSACTION); 171 mContext.startService(intent); 172 } 173 }); 174 break; 175 } 176 177 // Hide the error indicator. 178 mRightStatusIndicator.setVisibility(View.GONE); 179 180 drawLeftStatusIndicator(msgItem.mBoxId); 181 } 182 183 private void bindCommonMessage(final MessageItem msgItem) { 184 if (mDownloadButton != null) { 185 mDownloadButton.setVisibility(View.GONE); 186 mDownloadingLabel.setVisibility(View.GONE); 187 } 188 // Since the message text should be concatenated with the sender's 189 // address(or name), I have to display it here instead of 190 // displaying it by the Presenter. 191 mBodyTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 192 193 // Get and/or lazily set the formatted message from/on the 194 // MessageItem. Because the MessageItem instances come from a 195 // cache (currently of size ~50), the hit rate on avoiding the 196 // expensive formatMessage() call is very high. 197 CharSequence formattedMessage = msgItem.getCachedFormattedMessage(); 198 if (formattedMessage == null) { 199 formattedMessage = formatMessage(msgItem.mContact, msgItem.mBody, 200 msgItem.mSubject, msgItem.mTimestamp); 201 msgItem.setCachedFormattedMessage(formattedMessage); 202 } 203 mBodyTextView.setText(formattedMessage); 204 205 if (msgItem.isSms()) { 206 hideMmsViewIfNeeded(); 207 } else { 208 Presenter presenter = PresenterFactory.getPresenter( 209 "MmsThumbnailPresenter", mContext, 210 this, msgItem.mSlideshow); 211 presenter.present(); 212 213 if (msgItem.mAttachmentType != AttachmentEditor.TEXT_ONLY) { 214 inflateMmsView(); 215 mMmsView.setVisibility(View.VISIBLE); 216 setOnClickListener(msgItem); 217 drawPlaybackButton(msgItem); 218 } else { 219 hideMmsViewIfNeeded(); 220 } 221 } 222 223 drawLeftStatusIndicator(msgItem.mBoxId); 224 drawRightStatusIndicator(msgItem); 225 } 226 227 private void hideMmsViewIfNeeded() { 228 if (mMmsView != null) { 229 mMmsView.setVisibility(View.GONE); 230 } 231 } 232 233 public void startAudio() { 234 // TODO Auto-generated method stub 235 } 236 237 public void startVideo() { 238 // TODO Auto-generated method stub 239 } 240 241 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 242 // TODO Auto-generated method stub 243 } 244 245 public void setImage(String name, Bitmap bitmap) { 246 inflateMmsView(); 247 248 if (null == bitmap) { 249 bitmap = BitmapFactory.decodeResource(getResources(), 250 R.drawable.ic_missing_thumbnail_picture); 251 } 252 mImageView.setImageBitmap(bitmap); 253 mImageView.setVisibility(VISIBLE); 254 } 255 256 private void inflateMmsView() { 257 if (mMmsView == null) { 258 //inflate the surrounding view_stub 259 findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE); 260 261 mMmsView = findViewById(R.id.mms_view); 262 mImageView = (ImageView) findViewById(R.id.image_view); 263 mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); 264 } 265 } 266 267 private void inflateDownloadControls() { 268 if (mDownloadButton == null) { 269 //inflate the download controls 270 findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); 271 mDownloadButton = (Button) findViewById(R.id.btn_download_msg); 272 mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); 273 } 274 } 275 276 private CharSequence formatMessage(String contact, String body, String subject, 277 String timestamp) { 278 SpannableStringBuilder buf = new SpannableStringBuilder(contact); 279 buf.append(": "); 280 buf.setSpan(STYLE_BOLD, 0, buf.length(), 281 Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 282 283 boolean hasSubject = !TextUtils.isEmpty(subject); 284 if (hasSubject) { 285 buf.append(mContext.getResources().getString(R.string.inline_subject, subject)); 286 } 287 288 if (!TextUtils.isEmpty(body)) { 289 if (hasSubject) { 290 buf.append(" - "); 291 } 292 SmileyParser parser = SmileyParser.getInstance(); 293 buf.append(parser.addSmileySpans(body)); 294 } 295 296 buf.append("\n"); 297 int startOffset = buf.length(); 298 299 // put a one pixel high spacer line between the message and the time stamp as requested 300 // by the spec. 301 buf.append("\n"); 302 buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(), 303 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 304 305 startOffset = buf.length(); 306 buf.append(timestamp); 307 buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(), 308 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 309 // Make the timestamp text not as dark 310 int color = mContext.getResources().getColor(R.color.timestamp_color); 311 buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(), 312 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 313 314 return buf; 315 } 316 317 private void drawPlaybackButton(MessageItem msgItem) { 318 switch (msgItem.mAttachmentType) { 319 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 320 case AttachmentEditor.AUDIO_ATTACHMENT: 321 case AttachmentEditor.VIDEO_ATTACHMENT: 322 // Show the 'Play' button and bind message info on it. 323 mSlideShowButton.setTag(msgItem); 324 // Set call-back for the 'Play' button. 325 mSlideShowButton.setOnClickListener(this); 326 mSlideShowButton.setVisibility(View.VISIBLE); 327 setLongClickable(true); 328 break; 329 default: 330 mSlideShowButton.setVisibility(View.GONE); 331 break; 332 } 333 } 334 335 // OnClick Listener for the playback button 336 public void onClick(View v) { 337 MessageItem mi = (MessageItem) v.getTag(); 338 switch (mi.mAttachmentType) { 339 case AttachmentEditor.VIDEO_ATTACHMENT: 340 MessageUtils.viewSimpleSlideshow(mContext, mi.mSlideshow); 341 break; 342 case AttachmentEditor.AUDIO_ATTACHMENT: 343 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 344 Intent intent = new Intent( 345 mContext, SlideshowActivity.class); 346 intent.setData(mi.mMessageUri); 347 mContext.startActivity(intent); 348 break; 349 } 350 } 351 352 public void onMessageListItemClick() { 353 URLSpan[] spans = mBodyTextView.getUrls(); 354 355 if (spans.length == 0) { 356 // Do nothing. 357 } else if (spans.length == 1) { 358 Uri uri = Uri.parse(spans[0].getURL()); 359 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 360 361 mContext.startActivity(intent); 362 } else { 363 final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); 364 365 ArrayAdapter<String> adapter = 366 new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) { 367 public View getView(int position, View convertView, ViewGroup parent) { 368 View v = super.getView(position, convertView, parent); 369 try { 370 String url = getItem(position).toString(); 371 TextView tv = (TextView) v; 372 Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 373 if (d != null) { 374 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 375 tv.setCompoundDrawablePadding(10); 376 tv.setCompoundDrawables(d, null, null, null); 377 } 378 final String telPrefix = "tel:"; 379 if (url.startsWith(telPrefix)) { 380 url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length())); 381 } 382 tv.setText(url); 383 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 384 ; 385 } 386 return v; 387 } 388 }; 389 390 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 391 392 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 393 public final void onClick(DialogInterface dialog, int which) { 394 if (which >= 0) { 395 Uri uri = Uri.parse(urls.get(which)); 396 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 397 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); 398 mContext.startActivity(intent); 399 } 400 } 401 }; 402 403 b.setTitle(R.string.select_link_title); 404 b.setCancelable(true); 405 b.setAdapter(adapter, click); 406 407 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 408 public final void onClick(DialogInterface dialog, int which) { 409 dialog.dismiss(); 410 } 411 }); 412 413 b.show(); 414 } 415 } 416 417 418 private void setOnClickListener(final MessageItem msgItem) { 419 switch(msgItem.mAttachmentType) { 420 case AttachmentEditor.IMAGE_ATTACHMENT: 421 case AttachmentEditor.VIDEO_ATTACHMENT: 422 mImageView.setOnClickListener(new OnClickListener() { 423 public void onClick(View v) { 424 MessageUtils.viewSimpleSlideshow(mContext, msgItem.mSlideshow); 425 } 426 }); 427 mImageView.setOnLongClickListener(new OnLongClickListener() { 428 public boolean onLongClick(View v) { 429 return v.showContextMenu(); 430 } 431 }); 432 break; 433 434 default: 435 mImageView.setOnClickListener(null); 436 break; 437 } 438 } 439 440 private void drawLeftStatusIndicator(int msgBoxId) { 441 switch (msgBoxId) { 442 case Mms.MESSAGE_BOX_INBOX: 443 mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue); 444 break; 445 446 case Mms.MESSAGE_BOX_DRAFTS: 447 case Sms.MESSAGE_TYPE_FAILED: 448 case Sms.MESSAGE_TYPE_QUEUED: 449 case Mms.MESSAGE_BOX_OUTBOX: 450 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 451 break; 452 453 default: 454 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 455 break; 456 } 457 } 458 459 public static boolean isFailedMessage(MessageItem msgItem) { 460 boolean isFailedMms = msgItem.isMms() 461 && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT); 462 boolean isFailedSms = msgItem.isSms() 463 && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED); 464 return isFailedMms || isFailedSms; 465 } 466 467 private void setErrorIndicatorClickListener(final MessageItem msgItem) { 468 String type = msgItem.mType; 469 final int what; 470 if (type.equals("sms")) { 471 what = MSG_LIST_EDIT_SMS; 472 } else { 473 what = MSG_LIST_EDIT_MMS; 474 } 475 mRightStatusIndicator.setOnClickListener(new OnClickListener() { 476 public void onClick(View v) { 477 if (null != mHandler) { 478 Message msg = Message.obtain(mHandler, what); 479 msg.obj = new Long(msgItem.mMsgId); 480 msg.sendToTarget(); 481 } 482 } 483 }); 484 } 485 486 private void drawRightStatusIndicator(MessageItem msgItem) { 487 if (msgItem.isOutgoingMessage()) { 488 if (isFailedMessage(msgItem)) { 489 mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); 490 setErrorIndicatorClickListener(msgItem); 491 } else { 492 mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending); 493 } 494 mRightStatusIndicator.setVisibility(View.VISIBLE); 495 } else if (msgItem.mDeliveryReport || msgItem.mReadReport) { 496 mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details); 497 mRightStatusIndicator.setVisibility(View.VISIBLE); 498 } else { 499 mRightStatusIndicator.setVisibility(View.GONE); 500 } 501 } 502 503 public void setImageRegionFit(String fit) { 504 // TODO Auto-generated method stub 505 } 506 507 public void setImageVisibility(boolean visible) { 508 // TODO Auto-generated method stub 509 } 510 511 public void setText(String name, String text) { 512 // TODO Auto-generated method stub 513 } 514 515 public void setTextVisibility(boolean visible) { 516 // TODO Auto-generated method stub 517 } 518 519 public void setVideo(String name, Uri video) { 520 inflateMmsView(); 521 Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video); 522 if (null == bitmap) { 523 bitmap = BitmapFactory.decodeResource(getResources(), 524 R.drawable.ic_missing_thumbnail_video); 525 } 526 mImageView.setImageBitmap(bitmap); 527 mImageView.setVisibility(VISIBLE); 528 } 529 530 public void setVideoVisibility(boolean visible) { 531 // TODO Auto-generated method stub 532 } 533 534 public void stopAudio() { 535 // TODO Auto-generated method stub 536 } 537 538 public void stopVideo() { 539 // TODO Auto-generated method stub 540 } 541 542 public void reset() { 543 if (mImageView != null) { 544 mImageView.setVisibility(GONE); 545 } 546 } 547 548 public void setVisibility(boolean visible) { 549 // TODO Auto-generated method stub 550 } 551 552 public void pauseAudio() { 553 // TODO Auto-generated method stub 554 555 } 556 557 public void pauseVideo() { 558 // TODO Auto-generated method stub 559 560 } 561 562 public void seekAudio(int seekTo) { 563 // TODO Auto-generated method stub 564 565 } 566 567 public void seekVideo(int seekTo) { 568 // TODO Auto-generated method stub 569 570 } 571} 572