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