MessageListItem.java revision bc878528ed57d19bec31553cd75efaac1e0d3a5b
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 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 break; 341 default: 342 mSlideShowButton.setVisibility(View.GONE); 343 break; 344 } 345 } 346 347 // OnClick Listener for the playback button 348 public void onClick(View v) { 349 MessageItem mi = (MessageItem) v.getTag(); 350 switch (mi.mAttachmentType) { 351 case WorkingMessage.VIDEO: 352 case WorkingMessage.AUDIO: 353 case WorkingMessage.SLIDESHOW: 354 MessageUtils.viewMmsMessageAttachment(mContext, mi.mMessageUri, mi.mSlideshow); 355 break; 356 } 357 } 358 359 public void onMessageListItemClick() { 360 URLSpan[] spans = mBodyTextView.getUrls(); 361 362 if (spans.length == 0) { 363 // Do nothing. 364 } else if (spans.length == 1) { 365 Uri uri = Uri.parse(spans[0].getURL()); 366 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 367 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); 368 369 mContext.startActivity(intent); 370 } else { 371 final java.util.ArrayList<String> urls = MessageUtils.extractUris(spans); 372 373 ArrayAdapter<String> adapter = 374 new ArrayAdapter<String>(mContext, android.R.layout.select_dialog_item, urls) { 375 public View getView(int position, View convertView, ViewGroup parent) { 376 View v = super.getView(position, convertView, parent); 377 try { 378 String url = getItem(position).toString(); 379 TextView tv = (TextView) v; 380 Drawable d = mContext.getPackageManager().getActivityIcon(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); 381 if (d != null) { 382 d.setBounds(0, 0, d.getIntrinsicHeight(), d.getIntrinsicHeight()); 383 tv.setCompoundDrawablePadding(10); 384 tv.setCompoundDrawables(d, null, null, null); 385 } 386 final String telPrefix = "tel:"; 387 if (url.startsWith(telPrefix)) { 388 url = PhoneNumberUtils.formatNumber(url.substring(telPrefix.length())); 389 } 390 tv.setText(url); 391 } catch (android.content.pm.PackageManager.NameNotFoundException ex) { 392 ; 393 } 394 return v; 395 } 396 }; 397 398 AlertDialog.Builder b = new AlertDialog.Builder(mContext); 399 400 DialogInterface.OnClickListener click = new DialogInterface.OnClickListener() { 401 public final void onClick(DialogInterface dialog, int which) { 402 if (which >= 0) { 403 Uri uri = Uri.parse(urls.get(which)); 404 Intent intent = new Intent(Intent.ACTION_VIEW, uri); 405 intent.putExtra(Browser.EXTRA_APPLICATION_ID, mContext.getPackageName()); 406 mContext.startActivity(intent); 407 } 408 } 409 }; 410 411 b.setTitle(R.string.select_link_title); 412 b.setCancelable(true); 413 b.setAdapter(adapter, click); 414 415 b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { 416 public final void onClick(DialogInterface dialog, int which) { 417 dialog.dismiss(); 418 } 419 }); 420 421 b.show(); 422 } 423 } 424 425 426 private void setOnClickListener(final MessageItem msgItem) { 427 switch(msgItem.mAttachmentType) { 428 case WorkingMessage.IMAGE: 429 case WorkingMessage.VIDEO: 430 mImageView.setOnClickListener(new OnClickListener() { 431 public void onClick(View v) { 432 MessageUtils.viewMmsMessageAttachment(mContext, null, msgItem.mSlideshow); 433 } 434 }); 435 mImageView.setOnLongClickListener(new OnLongClickListener() { 436 public boolean onLongClick(View v) { 437 return v.showContextMenu(); 438 } 439 }); 440 break; 441 442 default: 443 mImageView.setOnClickListener(null); 444 break; 445 } 446 } 447 448 private void drawLeftStatusIndicator(int msgBoxId) { 449 switch (msgBoxId) { 450 case Mms.MESSAGE_BOX_INBOX: 451 mMsgListItem.setBackgroundResource(R.drawable.listitem_background_lightblue); 452 break; 453 454 case Mms.MESSAGE_BOX_DRAFTS: 455 case Sms.MESSAGE_TYPE_FAILED: 456 case Sms.MESSAGE_TYPE_QUEUED: 457 case Mms.MESSAGE_BOX_OUTBOX: 458 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 459 break; 460 461 default: 462 mMsgListItem.setBackgroundResource(R.drawable.listitem_background); 463 break; 464 } 465 } 466 467 public static boolean isFailedMessage(MessageItem msgItem) { 468 boolean isFailedMms = msgItem.isMms() 469 && (msgItem.mErrorType >= MmsSms.ERR_TYPE_GENERIC_PERMANENT); 470 boolean isFailedSms = msgItem.isSms() 471 && (msgItem.mBoxId == Sms.MESSAGE_TYPE_FAILED); 472 return isFailedMms || isFailedSms; 473 } 474 475 private void setErrorIndicatorClickListener(final MessageItem msgItem) { 476 String type = msgItem.mType; 477 final int what; 478 if (type.equals("sms")) { 479 what = MSG_LIST_EDIT_SMS; 480 } else { 481 what = MSG_LIST_EDIT_MMS; 482 } 483 mRightStatusIndicator.setOnClickListener(new OnClickListener() { 484 public void onClick(View v) { 485 if (null != mHandler) { 486 Message msg = Message.obtain(mHandler, what); 487 msg.obj = new Long(msgItem.mMsgId); 488 msg.sendToTarget(); 489 } 490 } 491 }); 492 } 493 494 private void drawRightStatusIndicator(MessageItem msgItem) { 495 if (msgItem.isOutgoingMessage()) { 496 if (isFailedMessage(msgItem)) { 497 mRightStatusIndicator.setImageResource(R.drawable.ic_sms_mms_not_delivered); 498 setErrorIndicatorClickListener(msgItem); 499 } else { 500 mRightStatusIndicator.setImageResource(R.drawable.ic_email_pending); 501 } 502 mRightStatusIndicator.setVisibility(View.VISIBLE); 503 } else if (msgItem.mDeliveryReport || msgItem.mReadReport) { 504 mRightStatusIndicator.setImageResource(R.drawable.ic_mms_message_details); 505 mRightStatusIndicator.setVisibility(View.VISIBLE); 506 } else if (msgItem.mLocked) { 507 mRightStatusIndicator.setImageResource(R.drawable.lock); 508 mRightStatusIndicator.setVisibility(View.VISIBLE); 509 } else { 510 mRightStatusIndicator.setVisibility(View.GONE); 511 } 512 } 513 514 public void setImageRegionFit(String fit) { 515 // TODO Auto-generated method stub 516 } 517 518 public void setImageVisibility(boolean visible) { 519 // TODO Auto-generated method stub 520 } 521 522 public void setText(String name, String text) { 523 // TODO Auto-generated method stub 524 } 525 526 public void setTextVisibility(boolean visible) { 527 // TODO Auto-generated method stub 528 } 529 530 public void setVideo(String name, Uri video) { 531 inflateMmsView(); 532 Bitmap bitmap = VideoAttachmentView.createVideoThumbnail(mContext, video); 533 if (null == bitmap) { 534 bitmap = BitmapFactory.decodeResource(getResources(), 535 R.drawable.ic_missing_thumbnail_video); 536 } 537 mImageView.setImageBitmap(bitmap); 538 mImageView.setVisibility(VISIBLE); 539 } 540 541 public void setVideoVisibility(boolean visible) { 542 // TODO Auto-generated method stub 543 } 544 545 public void stopAudio() { 546 // TODO Auto-generated method stub 547 } 548 549 public void stopVideo() { 550 // TODO Auto-generated method stub 551 } 552 553 public void reset() { 554 if (mImageView != null) { 555 mImageView.setVisibility(GONE); 556 } 557 } 558 559 public void setVisibility(boolean visible) { 560 // TODO Auto-generated method stub 561 } 562 563 public void pauseAudio() { 564 // TODO Auto-generated method stub 565 566 } 567 568 public void pauseVideo() { 569 // TODO Auto-generated method stub 570 571 } 572 573 public void seekAudio(int seekTo) { 574 // TODO Auto-generated method stub 575 576 } 577 578 public void seekVideo(int seekTo) { 579 // TODO Auto-generated method stub 580 581 } 582} 583