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