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