MessageUtils.java revision b6a0383dc77efea9a48e43e4e21a4290e89bf0a4
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.MmsConfig; 21import com.android.mms.R; 22import com.android.mms.LogTag; 23import com.android.mms.data.WorkingMessage; 24import com.android.mms.model.MediaModel; 25import com.android.mms.model.SlideModel; 26import com.android.mms.model.SlideshowModel; 27import com.android.mms.transaction.MmsMessageSender; 28import com.android.mms.util.AddressUtils; 29import com.google.android.mms.ContentType; 30import com.google.android.mms.MmsException; 31import com.google.android.mms.pdu.CharacterSets; 32import com.google.android.mms.pdu.EncodedStringValue; 33import com.google.android.mms.pdu.MultimediaMessagePdu; 34import com.google.android.mms.pdu.NotificationInd; 35import com.google.android.mms.pdu.PduBody; 36import com.google.android.mms.pdu.PduHeaders; 37import com.google.android.mms.pdu.PduPart; 38import com.google.android.mms.pdu.PduPersister; 39import com.google.android.mms.pdu.RetrieveConf; 40import com.google.android.mms.pdu.SendReq; 41import com.google.android.mms.util.SqliteWrapper; 42 43import android.app.Activity; 44import android.app.AlertDialog; 45import android.content.ContentUris; 46import android.content.Context; 47import android.content.DialogInterface; 48import android.content.Intent; 49import android.content.DialogInterface.OnCancelListener; 50import android.content.DialogInterface.OnClickListener; 51import android.content.res.Resources; 52import android.database.Cursor; 53import android.graphics.Bitmap; 54import android.graphics.Bitmap.CompressFormat; 55import android.media.RingtoneManager; 56import android.net.Uri; 57import android.os.Handler; 58import android.provider.Telephony.Mms; 59import android.provider.Telephony.Sms; 60import android.telephony.PhoneNumberUtils; 61import android.telephony.TelephonyManager; 62import android.text.TextUtils; 63import android.text.format.DateUtils; 64import android.text.format.Time; 65import android.text.style.URLSpan; 66import android.util.Log; 67import android.widget.Toast; 68 69import java.io.ByteArrayOutputStream; 70import java.io.IOException; 71import java.util.ArrayList; 72import java.util.HashMap; 73import java.util.Map; 74import java.util.concurrent.ConcurrentHashMap; 75 76/** 77 * An utility class for managing messages. 78 */ 79public class MessageUtils { 80 interface ResizeImageResultCallback { 81 void onResizeResult(PduPart part, boolean append); 82 } 83 84 private static final String TAG = LogTag.TAG; 85 private static String sLocalNumber; 86 87 // Cache of both groups of space-separated ids to their full 88 // comma-separated display names, as well as individual ids to 89 // display names. 90 // TODO: is it possible for canonical address ID keys to be 91 // re-used? SQLite does reuse IDs on NULL id_ insert, but does 92 // anything ever delete from the mmssms.db canonical_addresses 93 // table? Nothing that I could find. 94 private static final Map<String, String> sRecipientAddress = 95 new ConcurrentHashMap<String, String>(20 /* initial capacity */); 96 97 private MessageUtils() { 98 // Forbidden being instantiated. 99 } 100 101 public static String getMessageDetails(Context context, Cursor cursor, int size) { 102 if (cursor == null) { 103 return null; 104 } 105 106 if ("mms".equals(cursor.getString(MessageListAdapter.COLUMN_MSG_TYPE))) { 107 int type = cursor.getInt(MessageListAdapter.COLUMN_MMS_MESSAGE_TYPE); 108 switch (type) { 109 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 110 return getNotificationIndDetails(context, cursor); 111 case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: 112 case PduHeaders.MESSAGE_TYPE_SEND_REQ: 113 return getMultimediaMessageDetails(context, cursor, size); 114 default: 115 Log.w(TAG, "No details could be retrieved."); 116 return ""; 117 } 118 } else { 119 return getTextMessageDetails(context, cursor); 120 } 121 } 122 123 private static String getNotificationIndDetails(Context context, Cursor cursor) { 124 StringBuilder details = new StringBuilder(); 125 Resources res = context.getResources(); 126 127 long id = cursor.getLong(MessageListAdapter.COLUMN_ID); 128 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, id); 129 NotificationInd nInd; 130 131 try { 132 nInd = (NotificationInd) PduPersister.getPduPersister( 133 context).load(uri); 134 } catch (MmsException e) { 135 Log.e(TAG, "Failed to load the message: " + uri, e); 136 return context.getResources().getString(R.string.cannot_get_details); 137 } 138 139 // Message Type: Mms Notification. 140 details.append(res.getString(R.string.message_type_label)); 141 details.append(res.getString(R.string.multimedia_notification)); 142 143 // From: *** 144 String from = extractEncStr(context, nInd.getFrom()); 145 details.append('\n'); 146 details.append(res.getString(R.string.from_label)); 147 details.append(!TextUtils.isEmpty(from)? from: 148 res.getString(R.string.hidden_sender_address)); 149 150 // Date: *** 151 details.append('\n'); 152 details.append(res.getString( 153 R.string.expire_on, 154 MessageUtils.formatTimeStampString( 155 context, nInd.getExpiry() * 1000L, true))); 156 157 // Subject: *** 158 details.append('\n'); 159 details.append(res.getString(R.string.subject_label)); 160 161 EncodedStringValue subject = nInd.getSubject(); 162 if (subject != null) { 163 details.append(subject.getString()); 164 } 165 166 // Message class: Personal/Advertisement/Infomational/Auto 167 details.append('\n'); 168 details.append(res.getString(R.string.message_class_label)); 169 details.append(new String(nInd.getMessageClass())); 170 171 // Message size: *** KB 172 details.append('\n'); 173 details.append(res.getString(R.string.message_size_label)); 174 details.append(String.valueOf((nInd.getMessageSize() + 1023) / 1024)); 175 details.append(context.getString(R.string.kilobyte)); 176 177 return details.toString(); 178 } 179 180 private static String getMultimediaMessageDetails( 181 Context context, Cursor cursor, int size) { 182 int type = cursor.getInt(MessageListAdapter.COLUMN_MMS_MESSAGE_TYPE); 183 if (type == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) { 184 return getNotificationIndDetails(context, cursor); 185 } 186 187 StringBuilder details = new StringBuilder(); 188 Resources res = context.getResources(); 189 190 long id = cursor.getLong(MessageListAdapter.COLUMN_ID); 191 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, id); 192 MultimediaMessagePdu msg; 193 194 try { 195 msg = (MultimediaMessagePdu) PduPersister.getPduPersister( 196 context).load(uri); 197 } catch (MmsException e) { 198 Log.e(TAG, "Failed to load the message: " + uri, e); 199 return context.getResources().getString(R.string.cannot_get_details); 200 } 201 202 // Message Type: Text message. 203 details.append(res.getString(R.string.message_type_label)); 204 details.append(res.getString(R.string.multimedia_message)); 205 206 if (msg instanceof RetrieveConf) { 207 // From: *** 208 String from = extractEncStr(context, ((RetrieveConf) msg).getFrom()); 209 details.append('\n'); 210 details.append(res.getString(R.string.from_label)); 211 details.append(!TextUtils.isEmpty(from)? from: 212 res.getString(R.string.hidden_sender_address)); 213 } 214 215 // To: *** 216 details.append('\n'); 217 details.append(res.getString(R.string.to_address_label)); 218 EncodedStringValue[] to = msg.getTo(); 219 if (to != null) { 220 details.append(EncodedStringValue.concat(to)); 221 } 222 else { 223 Log.w(TAG, "recipient list is empty!"); 224 } 225 226 227 // Bcc: *** 228 if (msg instanceof SendReq) { 229 EncodedStringValue[] values = ((SendReq) msg).getBcc(); 230 if ((values != null) && (values.length > 0)) { 231 details.append('\n'); 232 details.append(res.getString(R.string.bcc_label)); 233 details.append(EncodedStringValue.concat(values)); 234 } 235 } 236 237 // Date: *** 238 details.append('\n'); 239 int msgBox = cursor.getInt(MessageListAdapter.COLUMN_MMS_MESSAGE_BOX); 240 if (msgBox == Mms.MESSAGE_BOX_DRAFTS) { 241 details.append(res.getString(R.string.saved_label)); 242 } else if (msgBox == Mms.MESSAGE_BOX_INBOX) { 243 details.append(res.getString(R.string.received_label)); 244 } else { 245 details.append(res.getString(R.string.sent_label)); 246 } 247 248 details.append(MessageUtils.formatTimeStampString( 249 context, msg.getDate() * 1000L, true)); 250 251 // Subject: *** 252 details.append('\n'); 253 details.append(res.getString(R.string.subject_label)); 254 255 EncodedStringValue subject = msg.getSubject(); 256 if (subject != null) { 257 String subStr = subject.getString(); 258 // Message size should include size of subject. 259 size += subStr.length(); 260 details.append(subStr); 261 } 262 263 // Priority: High/Normal/Low 264 details.append('\n'); 265 details.append(res.getString(R.string.priority_label)); 266 details.append(getPriorityDescription(context, msg.getPriority())); 267 268 // Message size: *** KB 269 details.append('\n'); 270 details.append(res.getString(R.string.message_size_label)); 271 details.append((size - 1)/1000 + 1); 272 details.append(" KB"); 273 274 return details.toString(); 275 } 276 277 private static String getTextMessageDetails(Context context, Cursor cursor) { 278 StringBuilder details = new StringBuilder(); 279 Resources res = context.getResources(); 280 281 // Message Type: Text message. 282 details.append(res.getString(R.string.message_type_label)); 283 details.append(res.getString(R.string.text_message)); 284 285 // Address: *** 286 details.append('\n'); 287 int smsType = cursor.getInt(MessageListAdapter.COLUMN_SMS_TYPE); 288 if (Sms.isOutgoingFolder(smsType)) { 289 details.append(res.getString(R.string.to_address_label)); 290 } else { 291 details.append(res.getString(R.string.from_label)); 292 } 293 details.append(cursor.getString(MessageListAdapter.COLUMN_SMS_ADDRESS)); 294 295 // Date: *** 296 details.append('\n'); 297 if (smsType == Sms.MESSAGE_TYPE_DRAFT) { 298 details.append(res.getString(R.string.saved_label)); 299 } else if (smsType == Sms.MESSAGE_TYPE_INBOX) { 300 details.append(res.getString(R.string.received_label)); 301 } else { 302 details.append(res.getString(R.string.sent_label)); 303 } 304 305 long date = cursor.getLong(MessageListAdapter.COLUMN_SMS_DATE); 306 details.append(MessageUtils.formatTimeStampString(context, date, true)); 307 308 return details.toString(); 309 } 310 311 static private String getPriorityDescription(Context context, int PriorityValue) { 312 Resources res = context.getResources(); 313 switch(PriorityValue) { 314 case PduHeaders.PRIORITY_HIGH: 315 return res.getString(R.string.priority_high); 316 case PduHeaders.PRIORITY_LOW: 317 return res.getString(R.string.priority_low); 318 case PduHeaders.PRIORITY_NORMAL: 319 default: 320 return res.getString(R.string.priority_normal); 321 } 322 } 323 324 public static int getAttachmentType(SlideshowModel model) { 325 if (model == null) { 326 return WorkingMessage.TEXT; 327 } 328 329 int numberOfSlides = model.size(); 330 if (numberOfSlides > 1) { 331 return WorkingMessage.SLIDESHOW; 332 } else if (numberOfSlides == 1) { 333 // Only one slide in the slide-show. 334 SlideModel slide = model.get(0); 335 if (slide.hasVideo()) { 336 return WorkingMessage.VIDEO; 337 } 338 339 if (slide.hasAudio() && slide.hasImage()) { 340 return WorkingMessage.SLIDESHOW; 341 } 342 343 if (slide.hasAudio()) { 344 return WorkingMessage.AUDIO; 345 } 346 347 if (slide.hasImage()) { 348 return WorkingMessage.IMAGE; 349 } 350 351 if (slide.hasText()) { 352 return WorkingMessage.TEXT; 353 } 354 } 355 356 return WorkingMessage.TEXT; 357 } 358 359 public static String formatTimeStampString(Context context, long when) { 360 return formatTimeStampString(context, when, false); 361 } 362 363 public static String formatTimeStampString(Context context, long when, boolean fullFormat) { 364 Time then = new Time(); 365 then.set(when); 366 Time now = new Time(); 367 now.setToNow(); 368 369 // Basic settings for formatDateTime() we want for all cases. 370 int format_flags = DateUtils.FORMAT_NO_NOON_MIDNIGHT | 371 DateUtils.FORMAT_ABBREV_ALL | 372 DateUtils.FORMAT_CAP_AMPM; 373 374 // If the message is from a different year, show the date and year. 375 if (then.year != now.year) { 376 format_flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE; 377 } else if (then.yearDay != now.yearDay) { 378 // If it is from a different day than today, show only the date. 379 format_flags |= DateUtils.FORMAT_SHOW_DATE; 380 } else { 381 // Otherwise, if the message is from today, show the time. 382 format_flags |= DateUtils.FORMAT_SHOW_TIME; 383 } 384 385 // If the caller has asked for full details, make sure to show the date 386 // and time no matter what we've determined above (but still make showing 387 // the year only happen if it is a different year from today). 388 if (fullFormat) { 389 format_flags |= (DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME); 390 } 391 392 return DateUtils.formatDateTime(context, when, format_flags); 393 } 394 395 /** 396 * @parameter recipientIds space-separated list of ids 397 */ 398 public static String getRecipientsByIds(Context context, String recipientIds, 399 boolean allowQuery) { 400 String value = sRecipientAddress.get(recipientIds); 401 if (value != null) { 402 return value; 403 } 404 if (!TextUtils.isEmpty(recipientIds)) { 405 StringBuilder addressBuf = extractIdsToAddresses( 406 context, recipientIds, allowQuery); 407 if (addressBuf == null) { 408 // temporary error? Don't memoize. 409 return ""; 410 } 411 value = addressBuf.toString(); 412 } else { 413 value = ""; 414 } 415 sRecipientAddress.put(recipientIds, value); 416 return value; 417 } 418 419 private static StringBuilder extractIdsToAddresses(Context context, String recipients, 420 boolean allowQuery) { 421 StringBuilder addressBuf = new StringBuilder(); 422 String[] recipientIds = recipients.split(" "); 423 boolean firstItem = true; 424 for (String recipientId : recipientIds) { 425 String value = sRecipientAddress.get(recipientId); 426 427 if (value == null) { 428 if (!allowQuery) { 429 // when allowQuery is false, if any value from sRecipientAddress.get() is null, 430 // return null for the whole thing. We don't want to stick partial result 431 // into sRecipientAddress for multiple recipient ids. 432 return null; 433 } 434 435 Uri uri = Uri.parse("content://mms-sms/canonical-address/" + recipientId); 436 Cursor c = SqliteWrapper.query(context, context.getContentResolver(), 437 uri, null, null, null, null); 438 if (c != null) { 439 try { 440 if (c.moveToFirst()) { 441 value = c.getString(0); 442 sRecipientAddress.put(recipientId, value); 443 } 444 } finally { 445 c.close(); 446 } 447 } 448 } 449 if (value == null) { 450 continue; 451 } 452 if (firstItem) { 453 firstItem = false; 454 } else { 455 addressBuf.append(";"); 456 } 457 addressBuf.append(value); 458 } 459 460 return (addressBuf.length() == 0) ? null : addressBuf; 461 } 462 463 public static void selectAudio(Context context, int requestCode) { 464 if (context instanceof Activity) { 465 Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); 466 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false); 467 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); 468 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_INCLUDE_DRM, false); 469 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, 470 context.getString(R.string.select_audio)); 471 ((Activity) context).startActivityForResult(intent, requestCode); 472 } 473 } 474 475 public static void recordSound(Context context, int requestCode) { 476 if (context instanceof Activity) { 477 Intent intent = new Intent(Intent.ACTION_GET_CONTENT); 478 intent.setType(ContentType.AUDIO_AMR); 479 intent.setClassName("com.android.soundrecorder", 480 "com.android.soundrecorder.SoundRecorder"); 481 482 ((Activity) context).startActivityForResult(intent, requestCode); 483 } 484 } 485 486 public static void selectVideo(Context context, int requestCode) { 487 selectMediaByType(context, requestCode, ContentType.VIDEO_UNSPECIFIED); 488 } 489 490 public static void selectImage(Context context, int requestCode) { 491 selectMediaByType(context, requestCode, ContentType.IMAGE_UNSPECIFIED); 492 } 493 494 private static void selectMediaByType( 495 Context context, int requestCode, String contentType) { 496 if (context instanceof Activity) { 497 498 Intent innerIntent = new Intent(Intent.ACTION_GET_CONTENT); 499 500 innerIntent.setType(contentType); 501 502 Intent wrapperIntent = Intent.createChooser(innerIntent, null); 503 504 ((Activity) context).startActivityForResult(wrapperIntent, requestCode); 505 } 506 } 507 508 public static void viewSimpleSlideshow(Context context, SlideshowModel slideshow) { 509 if (!slideshow.isSimple()) { 510 throw new IllegalArgumentException( 511 "viewSimpleSlideshow() called on a non-simple slideshow"); 512 } 513 SlideModel slide = slideshow.get(0); 514 MediaModel mm = null; 515 if (slide.hasImage()) { 516 mm = slide.getImage(); 517 } else if (slide.hasVideo()) { 518 mm = slide.getVideo(); 519 } 520 521 Intent intent = new Intent(Intent.ACTION_VIEW); 522 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 523 524 String contentType; 525 if (mm.isDrmProtected()) { 526 contentType = mm.getDrmObject().getContentType(); 527 } else { 528 contentType = mm.getContentType(); 529 } 530 intent.setDataAndType(mm.getUri(), contentType); 531 context.startActivity(intent); 532 } 533 534 public static void showErrorDialog(Context context, 535 String title, String message) { 536 AlertDialog.Builder builder = new AlertDialog.Builder(context); 537 538 builder.setIcon(R.drawable.ic_sms_mms_not_delivered); 539 builder.setTitle(title); 540 builder.setMessage(message); 541 builder.setPositiveButton(android.R.string.ok, null); 542 builder.show(); 543 } 544 545 /** 546 * The quality parameter which is used to compress JPEG images. 547 */ 548 public static final int IMAGE_COMPRESSION_QUALITY = 80; 549 /** 550 * The minimum quality parameter which is used to compress JPEG images. 551 */ 552 public static final int MINIMUM_IMAGE_COMPRESSION_QUALITY = 50; 553 554 public static Uri saveBitmapAsPart(Context context, Uri messageUri, Bitmap bitmap) 555 throws MmsException { 556 557 ByteArrayOutputStream os = new ByteArrayOutputStream(); 558 bitmap.compress(CompressFormat.JPEG, IMAGE_COMPRESSION_QUALITY, os); 559 560 PduPart part = new PduPart(); 561 562 part.setContentType("image/jpeg".getBytes()); 563 String contentId = "Image" + System.currentTimeMillis(); 564 part.setContentLocation((contentId + ".jpg").getBytes()); 565 part.setContentId(contentId.getBytes()); 566 part.setData(os.toByteArray()); 567 568 Uri retVal = PduPersister.getPduPersister(context).persistPart(part, 569 ContentUris.parseId(messageUri)); 570 571 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 572 log("saveBitmapAsPart: persisted part with uri=" + retVal); 573 } 574 575 return retVal; 576 } 577 578 /** 579 * Message overhead that reduces the maximum image byte size. 580 * 5000 is a realistic overhead number that allows for user to also include 581 * a small MIDI file or a couple pages of text along with the picture. 582 */ 583 public static final int MESSAGE_OVERHEAD = 5000; 584 585 public static void resizeImageAsync(final Context context, 586 final Uri imageUri, final Handler handler, 587 final ResizeImageResultCallback cb, 588 final boolean append) { 589 590 // Show a progress toast if the resize hasn't finished 591 // within one second. 592 // Stash the runnable for showing it away so we can cancel 593 // it later if the resize completes ahead of the deadline. 594 final Runnable showProgress = new Runnable() { 595 public void run() { 596 Toast.makeText(context, R.string.compressing, Toast.LENGTH_SHORT).show(); 597 } 598 }; 599 // Schedule it for one second from now. 600 handler.postDelayed(showProgress, 1000); 601 602 new Thread(new Runnable() { 603 public void run() { 604 final PduPart part; 605 try { 606 UriImage image = new UriImage(context, imageUri); 607 part = image.getResizedImageAsPart( 608 MmsConfig.getMaxImageWidth(), 609 MmsConfig.getMaxImageHeight(), 610 MmsConfig.getMaxMessageSize() - MESSAGE_OVERHEAD); 611 } finally { 612 // Cancel pending show of the progress toast if necessary. 613 handler.removeCallbacks(showProgress); 614 } 615 616 handler.post(new Runnable() { 617 public void run() { 618 cb.onResizeResult(part, append); 619 } 620 }); 621 } 622 }).start(); 623 } 624 625 public static void showDiscardDraftConfirmDialog(Context context, 626 OnClickListener listener) { 627 new AlertDialog.Builder(context) 628 .setIcon(android.R.drawable.ic_dialog_alert) 629 .setTitle(R.string.discard_message) 630 .setMessage(R.string.discard_message_reason) 631 .setPositiveButton(R.string.yes, listener) 632 .setNegativeButton(R.string.no, null) 633 .show(); 634 } 635 636 public static String getLocalNumber() { 637 if (null == sLocalNumber) { 638 sLocalNumber = TelephonyManager.getDefault().getLine1Number(); 639 } 640 return sLocalNumber; 641 } 642 643 public static boolean isLocalNumber(String number) { 644 if (Mms.isEmailAddress(number)) { 645 return false; 646 } 647 648 return PhoneNumberUtils.compare(number, getLocalNumber()); 649 } 650 651 public static void handleReadReport(final Context context, 652 final long threadId, 653 final int status, 654 final Runnable callback) { 655 String selection = Mms.MESSAGE_TYPE + " = " + PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF 656 + " AND " + Mms.READ + " = 0" 657 + " AND " + Mms.READ_REPORT + " = " + PduHeaders.VALUE_YES; 658 659 if (threadId != -1) { 660 selection = selection + " AND " + Mms.THREAD_ID + " = " + threadId; 661 } 662 663 final Cursor c = SqliteWrapper.query(context, context.getContentResolver(), 664 Mms.Inbox.CONTENT_URI, new String[] {Mms._ID, Mms.MESSAGE_ID}, 665 selection, null, null); 666 667 if (c == null) { 668 return; 669 } 670 671 final Map<String, String> map = new HashMap<String, String>(); 672 try { 673 if (c.getCount() == 0) { 674 if (callback != null) { 675 callback.run(); 676 } 677 return; 678 } 679 680 while (c.moveToNext()) { 681 Uri uri = ContentUris.withAppendedId(Mms.CONTENT_URI, c.getLong(0)); 682 map.put(c.getString(1), AddressUtils.getFrom(context, uri)); 683 } 684 } finally { 685 c.close(); 686 } 687 688 OnClickListener positiveListener = new OnClickListener() { 689 public void onClick(DialogInterface dialog, int which) { 690 for (final Map.Entry<String, String> entry : map.entrySet()) { 691 MmsMessageSender.sendReadRec(context, entry.getValue(), 692 entry.getKey(), status); 693 } 694 695 if (callback != null) { 696 callback.run(); 697 } 698 } 699 }; 700 701 OnClickListener negativeListener = new OnClickListener() { 702 public void onClick(DialogInterface dialog, int which) { 703 if (callback != null) { 704 callback.run(); 705 } 706 } 707 }; 708 709 OnCancelListener cancelListener = new OnCancelListener() { 710 public void onCancel(DialogInterface dialog) { 711 if (callback != null) { 712 callback.run(); 713 } 714 } 715 }; 716 717 confirmReadReportDialog(context, positiveListener, 718 negativeListener, 719 cancelListener); 720 } 721 722 private static void confirmReadReportDialog(Context context, 723 OnClickListener positiveListener, OnClickListener negativeListener, 724 OnCancelListener cancelListener) { 725 AlertDialog.Builder builder = new AlertDialog.Builder(context); 726 builder.setCancelable(true); 727 builder.setTitle(R.string.confirm); 728 builder.setMessage(R.string.message_send_read_report); 729 builder.setPositiveButton(R.string.yes, positiveListener); 730 builder.setNegativeButton(R.string.no, negativeListener); 731 builder.setOnCancelListener(cancelListener); 732 builder.show(); 733 } 734 735 public static String extractEncStrFromCursor(Cursor cursor, 736 int columnRawBytes, int columnCharset) { 737 String rawBytes = cursor.getString(columnRawBytes); 738 int charset = cursor.getInt(columnCharset); 739 740 if (TextUtils.isEmpty(rawBytes)) { 741 return ""; 742 } else if (charset == CharacterSets.ANY_CHARSET) { 743 return rawBytes; 744 } else { 745 return new EncodedStringValue(charset, PduPersister.getBytes(rawBytes)).getString(); 746 } 747 } 748 749 private static String extractEncStr(Context context, EncodedStringValue value) { 750 if (value != null) { 751 return value.getString(); 752 } else { 753 return ""; 754 } 755 } 756 757 public static ArrayList<String> extractUris(URLSpan[] spans) { 758 int size = spans.length; 759 ArrayList<String> accumulator = new ArrayList<String>(); 760 761 for (int i = 0; i < size; i++) { 762 accumulator.add(spans[i].getURL()); 763 } 764 return accumulator; 765 } 766 767 /** 768 * Play/view the message attachments. 769 * TOOD: We need to save the draft before launching another activity to view the attachments. 770 * This is hacky though since we will do saveDraft twice and slow down the UI. 771 * We should pass the slideshow in intent extra to the view activity instead of 772 * asking it to read attachments from database. 773 * @param context 774 * @param msgUri the MMS message URI in database 775 * @param slideshow the slideshow to save 776 * @param persister the PDU persister for updating the database 777 * @param sendReq the SendReq for updating the database 778 */ 779 public static void viewMmsMessageAttachment(Context context, Uri msgUri, 780 SlideshowModel slideshow) { 781 boolean isSimple = (slideshow == null) ? false : slideshow.isSimple(); 782 if (isSimple) { 783 // In attachment-editor mode, we only ever have one slide. 784 MessageUtils.viewSimpleSlideshow(context, slideshow); 785 } else { 786 // If a slideshow was provided, save it to disk first. 787 if (slideshow != null) { 788 PduPersister persister = PduPersister.getPduPersister(context); 789 try { 790 PduBody pb = slideshow.toPduBody(); 791 persister.updateParts(msgUri, pb); 792 slideshow.sync(pb); 793 } catch (MmsException e) { 794 Log.e(TAG, "Unable to save message for preview"); 795 return; 796 } 797 } 798 // Launch the slideshow activity to play/view. 799 Intent intent = new Intent(context, SlideshowActivity.class); 800 intent.setData(msgUri); 801 context.startActivity(intent); 802 } 803 } 804 805 public static void viewMmsMessageAttachment(Context context, WorkingMessage msg) { 806 SlideshowModel slideshow = msg.getSlideshow(); 807 if (slideshow == null) { 808 throw new IllegalStateException("msg.getSlideshow() == null"); 809 } 810 if (slideshow.isSimple()) { 811 MessageUtils.viewSimpleSlideshow(context, slideshow); 812 } else { 813 Uri uri = msg.saveAsMms(); 814 viewMmsMessageAttachment(context, uri, slideshow); 815 } 816 } 817 818 /** 819 * Debugging 820 */ 821 public static void writeHprofDataToFile(){ 822 String filename = "/sdcard/mms_oom_hprof_data"; 823 try { 824 android.os.Debug.dumpHprofData(filename); 825 Log.i(TAG, "##### written hprof data to " + filename); 826 } catch (IOException ex) { 827 Log.e(TAG, "writeHprofDataToFile: caught " + ex); 828 } 829 } 830 831 public static boolean isAlias(String string) { 832 if (!MmsConfig.isAliasEnabled()) { 833 return false; 834 } 835 836 if (TextUtils.isEmpty(string)) { 837 return false; 838 } 839 840 if (Mms.isPhoneNumber(string)) { 841 return false; 842 } 843 844 if (!isAlphaNumeric(string)) { 845 return false; 846 } 847 848 int len = string.length(); 849 850 if (len < MmsConfig.getAliasMinChars() || len > MmsConfig.getAliasMaxChars()) { 851 return false; 852 } 853 854 return true; 855 } 856 857 public static boolean isAlphaNumeric(String s) { 858 char[] chars = s.toCharArray(); 859 for (int x = 0; x < chars.length; x++) { 860 char c = chars[x]; 861 if ((c >= 'a') && (c <= 'z')) continue; 862 if ((c >= 'A') && (c <= 'Z')) continue; 863 if ((c >= '0') && (c <= '9')) continue; 864 return false; 865 } 866 return true; 867 } 868 869 private static void log(String msg) { 870 Log.d(TAG, "[MsgUtils] " + msg); 871 } 872} 873