MessageListItem.java revision d756a17492d468544d5544c61a1a96cc2db9c95b
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.data.WorkingMessage; 22import com.android.mms.transaction.Transaction; 23import com.android.mms.transaction.TransactionBundle; 24import com.android.mms.transaction.TransactionService; 25import com.android.mms.util.DownloadManager; 26import com.android.mms.util.SmileyParser; 27 28import com.google.android.mms.pdu.PduHeaders; 29import com.google.android.mms.pdu.PduPersister; 30 31import android.app.AlertDialog; 32import android.content.Context; 33import android.content.DialogInterface; 34import android.content.Intent; 35import android.graphics.Bitmap; 36import android.graphics.BitmapFactory; 37import android.graphics.Canvas; 38import android.graphics.Paint; 39import android.graphics.Typeface; 40import android.graphics.drawable.Drawable; 41import android.media.MediaMetadataRetriever; 42import android.media.MediaPlayer; 43import android.net.Uri; 44import android.os.Handler; 45import android.os.Message; 46import android.provider.Browser; 47import android.provider.Telephony.Mms; 48import android.provider.Telephony.MmsSms; 49import android.provider.Telephony.Sms; 50import android.telephony.PhoneNumberUtils; 51import android.text.Spannable; 52import android.text.SpannableStringBuilder; 53import android.text.TextUtils; 54import android.text.method.HideReturnsTransformationMethod; 55import android.text.style.AbsoluteSizeSpan; 56import android.text.style.BackgroundColorSpan; 57import android.text.style.ForegroundColorSpan; 58import android.text.style.LineBackgroundSpan; 59import android.text.style.StyleSpan; 60import android.text.style.URLSpan; 61import android.util.AttributeSet; 62import android.util.Log; 63import android.view.View; 64import android.view.ViewGroup; 65import android.view.View.OnClickListener; 66import android.widget.ArrayAdapter; 67import android.widget.Button; 68import android.widget.ImageButton; 69import android.widget.ImageView; 70import android.widget.LinearLayout; 71import android.widget.TextView; 72 73import java.io.IOException; 74import java.util.Map; 75 76/** 77 * This class provides view of a message in the messages list. 78 */ 79public class MessageListItem extends LinearLayout implements 80 SlideViewInterface, OnClickListener { 81 public static final String EXTRA_URLS = "com.android.mms.ExtraUrls"; 82 83 private static final String TAG = "MessageListItem"; 84 private static final StyleSpan STYLE_BOLD = new StyleSpan(Typeface.BOLD); 85 86 static final int MSG_LIST_EDIT_MMS = 1; 87 static final int MSG_LIST_EDIT_SMS = 2; 88 89 private View mMsgListItem; 90 private View mMmsView; 91 private ImageView mImageView; 92 private ImageView mRightStatusIndicator; 93 private ImageButton mSlideShowButton; 94 private TextView mBodyTextView; 95 private Button mDownloadButton; 96 private TextView mDownloadingLabel; 97 private Handler mHandler; 98 private MessageItem mMessageItem; 99 100 public MessageListItem(Context context) { 101 super(context); 102 } 103 104 public MessageListItem(Context context, AttributeSet attrs) { 105 super(context, attrs); 106 } 107 108 @Override 109 protected void onFinishInflate() { 110 super.onFinishInflate(); 111 112 mMsgListItem = findViewById(R.id.msg_list_item); 113 mBodyTextView = (TextView) findViewById(R.id.text_view); 114 mRightStatusIndicator = (ImageView) findViewById(R.id.right_status_indicator); 115 } 116 117 public void bind(MessageItem msgItem) { 118 mMessageItem = msgItem; 119 120 setLongClickable(false); 121 122 switch (msgItem.mMessageType) { 123 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 124 bindNotifInd(msgItem); 125 break; 126 default: 127 bindCommonMessage(msgItem); 128 break; 129 } 130 } 131 132 public MessageItem getMessageItem() { 133 return mMessageItem; 134 } 135 136 public void setMsgListItemHandler(Handler handler) { 137 mHandler = handler; 138 } 139 140 private void bindNotifInd(final MessageItem msgItem) { 141 hideMmsViewIfNeeded(); 142 143 String msgSizeText = mContext.getString(R.string.message_size_label) 144 + String.valueOf((msgItem.mMessageSize + 1023) / 1024) 145 + mContext.getString(R.string.kilobyte); 146 147 mBodyTextView.setText(formatMessage(msgItem.mContact, null, msgItem.mSubject, 148 msgSizeText + "\n" + msgItem.mTimestamp)); 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 // Get and/or lazily set the formatted message from/on the 196 // MessageItem. Because the MessageItem instances come from a 197 // cache (currently of size ~50), the hit rate on avoiding the 198 // expensive formatMessage() call is very high. 199 CharSequence formattedMessage = msgItem.getCachedFormattedMessage(); 200 if (formattedMessage == null) { 201 formattedMessage = formatMessage(msgItem.mContact, msgItem.mBody, 202 msgItem.mSubject, msgItem.mTimestamp); 203 msgItem.setCachedFormattedMessage(formattedMessage); 204 } 205 mBodyTextView.setText(formattedMessage); 206 207 if (msgItem.isSms()) { 208 hideMmsViewIfNeeded(); 209 } else { 210 Presenter presenter = PresenterFactory.getPresenter( 211 "MmsThumbnailPresenter", mContext, 212 this, msgItem.mSlideshow); 213 presenter.present(); 214 215 if (msgItem.mAttachmentType != WorkingMessage.TEXT) { 216 inflateMmsView(); 217 mMmsView.setVisibility(View.VISIBLE); 218 setOnClickListener(msgItem); 219 drawPlaybackButton(msgItem); 220 } else { 221 hideMmsViewIfNeeded(); 222 } 223 } 224 225 drawLeftStatusIndicator(msgItem.mBoxId); 226 drawRightStatusIndicator(msgItem); 227 } 228 229 private void hideMmsViewIfNeeded() { 230 if (mMmsView != null) { 231 mMmsView.setVisibility(View.GONE); 232 } 233 } 234 235 public void startAudio() { 236 // TODO Auto-generated method stub 237 } 238 239 public void startVideo() { 240 // TODO Auto-generated method stub 241 } 242 243 public void setAudio(Uri audio, String name, Map<String, ?> extras) { 244 // TODO Auto-generated method stub 245 } 246 247 public void setImage(String name, Bitmap bitmap) { 248 inflateMmsView(); 249 250 if (null == bitmap) { 251 bitmap = BitmapFactory.decodeResource(getResources(), 252 R.drawable.ic_missing_thumbnail_picture); 253 } 254 mImageView.setImageBitmap(bitmap); 255 mImageView.setVisibility(VISIBLE); 256 } 257 258 private void inflateMmsView() { 259 if (mMmsView == null) { 260 //inflate the surrounding view_stub 261 findViewById(R.id.mms_layout_view_stub).setVisibility(VISIBLE); 262 263 mMmsView = findViewById(R.id.mms_view); 264 mImageView = (ImageView) findViewById(R.id.image_view); 265 mSlideShowButton = (ImageButton) findViewById(R.id.play_slideshow_button); 266 } 267 } 268 269 private void inflateDownloadControls() { 270 if (mDownloadButton == null) { 271 //inflate the download controls 272 findViewById(R.id.mms_downloading_view_stub).setVisibility(VISIBLE); 273 mDownloadButton = (Button) findViewById(R.id.btn_download_msg); 274 mDownloadingLabel = (TextView) findViewById(R.id.label_downloading); 275 } 276 } 277 278 private CharSequence formatMessage(String contact, String body, String subject, 279 String timestamp) { 280 CharSequence template = mContext.getResources().getText(R.string.name_colon); 281 SpannableStringBuilder buf = 282 new SpannableStringBuilder(TextUtils.replace(template, 283 new String[] { "%s" }, 284 new CharSequence[] { contact })); 285 286 boolean hasSubject = !TextUtils.isEmpty(subject); 287 if (hasSubject) { 288 buf.append(mContext.getResources().getString(R.string.inline_subject, subject)); 289 } 290 291 if (!TextUtils.isEmpty(body)) { 292 if (hasSubject) { 293 buf.append(" - "); 294 } 295 SmileyParser parser = SmileyParser.getInstance(); 296 buf.append(parser.addSmileySpans(body)); 297 } 298 299 buf.append("\n"); 300 int startOffset = buf.length(); 301 302 // put a one pixel high spacer line between the message and the time stamp as requested 303 // by the spec. 304 buf.append("\n"); 305 buf.setSpan(new AbsoluteSizeSpan(3), startOffset, buf.length(), 306 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 307 308 startOffset = buf.length(); 309 buf.append(timestamp); 310 buf.setSpan(new AbsoluteSizeSpan(12), startOffset, buf.length(), 311 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 312 // Make the timestamp text not as dark 313 int color = mContext.getResources().getColor(R.color.timestamp_color); 314 buf.setSpan(new ForegroundColorSpan(color), startOffset, buf.length(), 315 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 316 317 return buf; 318 } 319 320 private void drawPlaybackButton(MessageItem msgItem) { 321 switch (msgItem.mAttachmentType) { 322 case WorkingMessage.SLIDESHOW: 323 case WorkingMessage.AUDIO: 324 case WorkingMessage.VIDEO: 325 // Show the 'Play' button and bind message info on it. 326 mSlideShowButton.setTag(msgItem); 327 // Set call-back for the 'Play' button. 328 mSlideShowButton.setOnClickListener(this); 329 mSlideShowButton.setVisibility(View.VISIBLE); 330 setLongClickable(true); 331 break; 332 default: 333 mSlideShowButton.setVisibility(View.GONE); 334 break; 335 } 336 } 337 338 // OnClick Listener for the playback button 339 public void onClick(View v) { 340 MessageItem mi = (MessageItem) v.getTag(); 341 switch (mi.mAttachmentType) { 342 case WorkingMessage.VIDEO: 343 case WorkingMessage.AUDIO: 344 case WorkingMessage.SLIDESHOW: 345 MessageUtils.viewMmsMessageAttachment(mContext, mi.mMessageUri, mi.mSlideshow); 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 WorkingMessage.IMAGE: 420 case WorkingMessage.VIDEO: 421 mImageView.setOnClickListener(new OnClickListener() { 422 public void onClick(View v) { 423 MessageUtils.viewMmsMessageAttachment(mContext, null, msgItem.mSlideshow); 424 } 425 }); 426 mImageView.setOnLongClickListener(new OnLongClickListener() { 427 public boolean onLongClick(View v) { 428 return v.showContextMenu(); 429 } 430 }); 431 break; 432 433 default: 434 mImageView.setOnClickListener(null); 435 break; 436 } 437 } 438 439 private void drawLeftStatusIndicator(int msgBoxId) { 440 switch (msgBoxId) { 441 case Mms.MESSAGE_BOX_INBOX: 442 mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue); 443 break; 444 445 case Mms.MESSAGE_BOX_DRAFTS: 446 case Sms.MESSAGE_TYPE_FAILED: 447 case Sms.MESSAGE_TYPE_QUEUED: 448 case Mms.MESSAGE_BOX_OUTBOX: 449 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 450 break; 451 452 default: 453 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 454 break; 455 } 456 } 457 458 public static boolean isFailedMessage(MessageItem msgItem) { 459 boolean isFailedMms = msgItem.isMms() 460 && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT); 461 boolean isFailedSms = msgItem.isSms() 462 && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED); 463 return isFailedMms || isFailedSms; 464 } 465 466 private void setErrorIndicatorClickListener(final MessageItem msgItem) { 467 String type = msgItem.mType; 468 final int what; 469 if (type.equals("sms")) { 470 what = MSG_LIST_EDIT_SMS; 471 } else { 472 what = MSG_LIST_EDIT_MMS; 473 } 474 mRightStatusIndicator.setOnClickListener(new OnClickListener() { 475 public void onClick(View v) { 476 if (null != mHandler) { 477 Message msg = Message.obtain(mHandler, what); 478 msg.obj = new Long(msgItem.mMsgId); 479 msg.sendToTarget(); 480 } 481 } 482 }); 483 } 484 485 private void drawRightStatusIndicator(MessageItem msgItem) { 486 if (msgItem.isOutgoingMessage()) { 487 if (isFailedMessage(msgItem)) { 488 mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); 489 setErrorIndicatorClickListener(msgItem); 490 } else { 491 mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending); 492 } 493 mRightStatusIndicator.setVisibility(View.VISIBLE); 494 } else if (msgItem.mDeliveryReport || msgItem.mReadReport) { 495 mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details); 496 mRightStatusIndicator.setVisibility(View.VISIBLE); 497 } else { 498 mRightStatusIndicator.setVisibility(View.GONE); 499 } 500 } 501 502 public void setImageRegionFit(String fit) { 503 // TODO Auto-generated method stub 504 } 505 506 public void setImageVisibility(boolean visible) { 507 // TODO Auto-generated method stub 508 } 509 510 public void setText(String name, String text) { 511 // TODO Auto-generated method stub 512 } 513 514 public void setTextVisibility(boolean visible) { 515 // TODO Auto-generated method stub 516 } 517 518 public void setVideo(String name, Uri video) { 519 inflateMmsView(); 520 Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video); 521 if (null == bitmap) { 522 bitmap = BitmapFactory.decodeResource(getResources(), 523 R.drawable.ic_missing_thumbnail_video); 524 } 525 mImageView.setImageBitmap(bitmap); 526 mImageView.setVisibility(VISIBLE); 527 } 528 529 public void setVideoVisibility(boolean visible) { 530 // TODO Auto-generated method stub 531 } 532 533 public void stopAudio() { 534 // TODO Auto-generated method stub 535 } 536 537 public void stopVideo() { 538 // TODO Auto-generated method stub 539 } 540 541 public void reset() { 542 if (mImageView != null) { 543 mImageView.setVisibility(GONE); 544 } 545 } 546 547 public void setVisibility(boolean visible) { 548 // TODO Auto-generated method stub 549 } 550 551 public void pauseAudio() { 552 // TODO Auto-generated method stub 553 554 } 555 556 public void pauseVideo() { 557 // TODO Auto-generated method stub 558 559 } 560 561 public void seekAudio(int seekTo) { 562 // TODO Auto-generated method stub 563 564 } 565 566 public void seekVideo(int seekTo) { 567 // TODO Auto-generated method stub 568 569 } 570} 571