MessageListItem.java revision c0c78f771e7ce097103241c0da02959319b58709
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 SpannableStringBuilder buf = new SpannableStringBuilder(contact); 280 buf.append(": "); 281 buf.setSpan(STYLE_BOLD, 0, buf.length(), 282 Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 283 284 boolean hasSubject = !TextUtils.isEmpty(subject); 285 if (hasSubject) { 286 buf.append(mContext.getResources().getString(R.string.inline_subject, subject)); 287 } 288 289 if (!TextUtils.isEmpty(body)) { 290 if (hasSubject) { 291 buf.append(" - "); 292 } 293 SmileyParser parser = SmileyParser.getInstance(); 294 buf.append(parser.addSmileySpans(body)); 295 } 296 297 buf.append("\n"); 298 int startOffset = buf.length(); 299 300 // put a one pixel high spacer line between the message and the time stamp as requested 301 // by the spec. 302 buf.append("\n"); 303 buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(), 304 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 305 306 startOffset = buf.length(); 307 buf.append(timestamp); 308 buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(), 309 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 310 // Make the timestamp text not as dark 311 int color = mContext.getResources().getColor(R.color.timestamp_color); 312 buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(), 313 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 314 315 return buf; 316 } 317 318 private void drawPlaybackButton(MessageItem msgItem) { 319 switch (msgItem.mAttachmentType) { 320 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 321 case AttachmentEditor.AUDIO_ATTACHMENT: 322 case AttachmentEditor.VIDEO_ATTACHMENT: 323 // Show the 'Play' button and bind message info on it. 324 mSlideShowButton.setTag(msgItem); 325 // Set call-back for the 'Play' button. 326 mSlideShowButton.setOnClickListener(this); 327 mSlideShowButton.setVisibility(View.VISIBLE); 328 setLongClickable(true); 329 break; 330 default: 331 mSlideShowButton.setVisibility(View.GONE); 332 break; 333 } 334 } 335 336 // OnClick Listener for the playback button 337 public void onClick(View v) { 338 MessageItem mi = (MessageItem) v.getTag(); 339 switch (mi.mAttachmentType) { 340 case AttachmentEditor.VIDEO_ATTACHMENT: 341 case AttachmentEditor.AUDIO_ATTACHMENT: 342 case AttachmentEditor.SLIDESHOW_ATTACHMENT: 343 MessageUtils.viewMmsMessageAttachment(mContext, 344 mi.mMessageUri, mi.mSlideshow, null /* persister */); 345 break; 346 } 347 } 348 349 public void onMessageListItemClick() { 350 URLSpan[] spans = mBodyTextView.getUrls(); 351 352 if (spans.length == 0) { 353 // Do nothing. 354 } else if (spans.length == 1) { 355 Uri uri = Uri.parse(spans[0].getURL()); 356 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 357 358 mContext.startActivity(intent); 359 } else { 360 final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); 361 362 ArrayAdapter<String> adapter = 363 new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) { 364 public View getView(int position, View convertView, ViewGroup parent) { 365 View v = super.getView(position, convertView, parent); 366 try { 367 String url = getItem(position).toString(); 368 TextView tv = (TextView) v; 369 Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 370 if (d != null) { 371 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 372 tv.setCompoundDrawablePadding(10); 373 tv.setCompoundDrawables(d, null, null, null); 374 } 375 final String telPrefix = "tel:"; 376 if (url.startsWith(telPrefix)) { 377 url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length())); 378 } 379 tv.setText(url); 380 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 381 ; 382 } 383 return v; 384 } 385 }; 386 387 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 388 389 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 390 public final void onClick(DialogInterface dialog, int which) { 391 if (which >= 0) { 392 Uri uri = Uri.parse(urls.get(which)); 393 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 394 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); 395 mContext.startActivity(intent); 396 } 397 } 398 }; 399 400 b.setTitle(R.string.select_link_title); 401 b.setCancelable(true); 402 b.setAdapter(adapter, click); 403 404 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 405 public final void onClick(DialogInterface dialog, int which) { 406 dialog.dismiss(); 407 } 408 }); 409 410 b.show(); 411 } 412 } 413 414 415 private void setOnClickListener(final MessageItem msgItem) { 416 switch(msgItem.mAttachmentType) { 417 case AttachmentEditor.IMAGE_ATTACHMENT: 418 case AttachmentEditor.VIDEO_ATTACHMENT: 419 mImageView.setOnClickListener(new OnClickListener() { 420 public void onClick(View v) { 421 MessageUtils.viewMmsMessageAttachment(mContext, 422 null /* uri */, msgItem.mSlideshow, null /* persister */); 423 } 424 }); 425 mImageView.setOnLongClickListener(new OnLongClickListener() { 426 public boolean onLongClick(View v) { 427 return v.showContextMenu(); 428 } 429 }); 430 break; 431 432 default: 433 mImageView.setOnClickListener(null); 434 break; 435 } 436 } 437 438 private void drawLeftStatusIndicator(int msgBoxId) { 439 switch (msgBoxId) { 440 case Mms.MESSAGE_BOX_INBOX: 441 mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue); 442 break; 443 444 case Mms.MESSAGE_BOX_DRAFTS: 445 case Sms.MESSAGE_TYPE_FAILED: 446 case Sms.MESSAGE_TYPE_QUEUED: 447 case Mms.MESSAGE_BOX_OUTBOX: 448 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 449 break; 450 451 default: 452 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 453 break; 454 } 455 } 456 457 public static boolean isFailedMessage(MessageItem msgItem) { 458 boolean isFailedMms = msgItem.isMms() 459 && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT); 460 boolean isFailedSms = msgItem.isSms() 461 && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED); 462 return isFailedMms || isFailedSms; 463 } 464 465 private void setErrorIndicatorClickListener(final MessageItem msgItem) { 466 String type = msgItem.mType; 467 final int what; 468 if (type.equals("sms")) { 469 what = MSG_LIST_EDIT_SMS; 470 } else { 471 what = MSG_LIST_EDIT_MMS; 472 } 473 mRightStatusIndicator.setOnClickListener(new OnClickListener() { 474 public void onClick(View v) { 475 if (null != mHandler) { 476 Message msg = Message.obtain(mHandler, what); 477 msg.obj = new Long(msgItem.mMsgId); 478 msg.sendToTarget(); 479 } 480 } 481 }); 482 } 483 484 private void drawRightStatusIndicator(MessageItem msgItem) { 485 if (msgItem.isOutgoingMessage()) { 486 if (isFailedMessage(msgItem)) { 487 mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); 488 setErrorIndicatorClickListener(msgItem); 489 } else { 490 mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending); 491 } 492 mRightStatusIndicator.setVisibility(View.VISIBLE); 493 } else if (msgItem.mDeliveryReport || msgItem.mReadReport) { 494 mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details); 495 mRightStatusIndicator.setVisibility(View.VISIBLE); 496 } else { 497 mRightStatusIndicator.setVisibility(View.GONE); 498 } 499 } 500 501 public void setImageRegionFit(String fit) { 502 // TODO Auto-generated method stub 503 } 504 505 public void setImageVisibility(boolean visible) { 506 // TODO Auto-generated method stub 507 } 508 509 public void setText(String name, String text) { 510 // TODO Auto-generated method stub 511 } 512 513 public void setTextVisibility(boolean visible) { 514 // TODO Auto-generated method stub 515 } 516 517 public void setVideo(String name, Uri video) { 518 inflateMmsView(); 519 Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video); 520 if (null == bitmap) { 521 bitmap = BitmapFactory.decodeResource(getResources(), 522 R.drawable.ic_missing_thumbnail_video); 523 } 524 mImageView.setImageBitmap(bitmap); 525 mImageView.setVisibility(VISIBLE); 526 } 527 528 public void setVideoVisibility(boolean visible) { 529 // TODO Auto-generated method stub 530 } 531 532 public void stopAudio() { 533 // TODO Auto-generated method stub 534 } 535 536 public void stopVideo() { 537 // TODO Auto-generated method stub 538 } 539 540 public void reset() { 541 if (mImageView != null) { 542 mImageView.setVisibility(GONE); 543 } 544 } 545 546 public void setVisibility(boolean visible) { 547 // TODO Auto-generated method stub 548 } 549 550 public void pauseAudio() { 551 // TODO Auto-generated method stub 552 553 } 554 555 public void pauseVideo() { 556 // TODO Auto-generated method stub 557 558 } 559 560 public void seekAudio(int seekTo) { 561 // TODO Auto-generated method stub 562 563 } 564 565 public void seekVideo(int seekTo) { 566 // TODO Auto-generated method stub 567 568 } 569} 570