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