PduPersister.java revision ab17014baf326d69a5bfd153622a1d7da6a4f9ce
1/* 2 * Copyright (C) 2007-2008 Esmertec AG. 3 * Copyright (C) 2007-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.google.android.mms.pdu; 19 20import com.google.android.mms.ContentType; 21import com.google.android.mms.InvalidHeaderValueException; 22import com.google.android.mms.MmsException; 23import com.google.android.mms.util.DownloadDrmHelper; 24import com.google.android.mms.util.DrmConvertSession; 25import com.google.android.mms.util.PduCache; 26import com.google.android.mms.util.PduCacheEntry; 27import com.google.android.mms.util.SqliteWrapper; 28 29import android.content.ContentResolver; 30import android.content.ContentUris; 31import android.content.ContentValues; 32import android.content.Context; 33import android.database.Cursor; 34import android.database.DatabaseUtils; 35import android.database.sqlite.SQLiteException; 36import android.drm.DrmManagerClient; 37import android.net.Uri; 38import android.provider.MediaStore; 39import android.provider.Telephony; 40import android.provider.Telephony.Mms; 41import android.provider.Telephony.MmsSms; 42import android.provider.Telephony.Threads; 43import android.provider.Telephony.Mms.Addr; 44import android.provider.Telephony.Mms.Part; 45import android.provider.Telephony.MmsSms.PendingMessages; 46import android.telephony.PhoneNumberUtils; 47import android.telephony.TelephonyManager; 48import android.text.TextUtils; 49import android.util.Log; 50 51import java.io.ByteArrayOutputStream; 52import java.io.File; 53import java.io.FileNotFoundException; 54import java.io.IOException; 55import java.io.InputStream; 56import java.io.OutputStream; 57import java.io.UnsupportedEncodingException; 58import java.util.ArrayList; 59import java.util.HashMap; 60import java.util.HashSet; 61import java.util.Map; 62import java.util.Set; 63import java.util.Map.Entry; 64 65import com.google.android.mms.pdu.EncodedStringValue; 66 67/** 68 * This class is the high-level manager of PDU storage. 69 */ 70public class PduPersister { 71 private static final String TAG = "PduPersister"; 72 private static final boolean DEBUG = false; 73 private static final boolean LOCAL_LOGV = false; 74 75 private static final long DUMMY_THREAD_ID = Long.MAX_VALUE; 76 77 /** 78 * The uri of temporary drm objects. 79 */ 80 public static final String TEMPORARY_DRM_OBJECT_URI = 81 "content://mms/" + Long.MAX_VALUE + "/part"; 82 /** 83 * Indicate that we transiently failed to process a MM. 84 */ 85 public static final int PROC_STATUS_TRANSIENT_FAILURE = 1; 86 /** 87 * Indicate that we permanently failed to process a MM. 88 */ 89 public static final int PROC_STATUS_PERMANENTLY_FAILURE = 2; 90 /** 91 * Indicate that we have successfully processed a MM. 92 */ 93 public static final int PROC_STATUS_COMPLETED = 3; 94 95 private static PduPersister sPersister; 96 private static final PduCache PDU_CACHE_INSTANCE; 97 98 private static final int[] ADDRESS_FIELDS = new int[] { 99 PduHeaders.BCC, 100 PduHeaders.CC, 101 PduHeaders.FROM, 102 PduHeaders.TO 103 }; 104 105 private static final String[] PDU_PROJECTION = new String[] { 106 Mms._ID, 107 Mms.MESSAGE_BOX, 108 Mms.THREAD_ID, 109 Mms.RETRIEVE_TEXT, 110 Mms.SUBJECT, 111 Mms.CONTENT_LOCATION, 112 Mms.CONTENT_TYPE, 113 Mms.MESSAGE_CLASS, 114 Mms.MESSAGE_ID, 115 Mms.RESPONSE_TEXT, 116 Mms.TRANSACTION_ID, 117 Mms.CONTENT_CLASS, 118 Mms.DELIVERY_REPORT, 119 Mms.MESSAGE_TYPE, 120 Mms.MMS_VERSION, 121 Mms.PRIORITY, 122 Mms.READ_REPORT, 123 Mms.READ_STATUS, 124 Mms.REPORT_ALLOWED, 125 Mms.RETRIEVE_STATUS, 126 Mms.STATUS, 127 Mms.DATE, 128 Mms.DELIVERY_TIME, 129 Mms.EXPIRY, 130 Mms.MESSAGE_SIZE, 131 Mms.SUBJECT_CHARSET, 132 Mms.RETRIEVE_TEXT_CHARSET, 133 }; 134 135 private static final int PDU_COLUMN_ID = 0; 136 private static final int PDU_COLUMN_MESSAGE_BOX = 1; 137 private static final int PDU_COLUMN_THREAD_ID = 2; 138 private static final int PDU_COLUMN_RETRIEVE_TEXT = 3; 139 private static final int PDU_COLUMN_SUBJECT = 4; 140 private static final int PDU_COLUMN_CONTENT_LOCATION = 5; 141 private static final int PDU_COLUMN_CONTENT_TYPE = 6; 142 private static final int PDU_COLUMN_MESSAGE_CLASS = 7; 143 private static final int PDU_COLUMN_MESSAGE_ID = 8; 144 private static final int PDU_COLUMN_RESPONSE_TEXT = 9; 145 private static final int PDU_COLUMN_TRANSACTION_ID = 10; 146 private static final int PDU_COLUMN_CONTENT_CLASS = 11; 147 private static final int PDU_COLUMN_DELIVERY_REPORT = 12; 148 private static final int PDU_COLUMN_MESSAGE_TYPE = 13; 149 private static final int PDU_COLUMN_MMS_VERSION = 14; 150 private static final int PDU_COLUMN_PRIORITY = 15; 151 private static final int PDU_COLUMN_READ_REPORT = 16; 152 private static final int PDU_COLUMN_READ_STATUS = 17; 153 private static final int PDU_COLUMN_REPORT_ALLOWED = 18; 154 private static final int PDU_COLUMN_RETRIEVE_STATUS = 19; 155 private static final int PDU_COLUMN_STATUS = 20; 156 private static final int PDU_COLUMN_DATE = 21; 157 private static final int PDU_COLUMN_DELIVERY_TIME = 22; 158 private static final int PDU_COLUMN_EXPIRY = 23; 159 private static final int PDU_COLUMN_MESSAGE_SIZE = 24; 160 private static final int PDU_COLUMN_SUBJECT_CHARSET = 25; 161 private static final int PDU_COLUMN_RETRIEVE_TEXT_CHARSET = 26; 162 163 private static final String[] PART_PROJECTION = new String[] { 164 Part._ID, 165 Part.CHARSET, 166 Part.CONTENT_DISPOSITION, 167 Part.CONTENT_ID, 168 Part.CONTENT_LOCATION, 169 Part.CONTENT_TYPE, 170 Part.FILENAME, 171 Part.NAME, 172 Part.TEXT 173 }; 174 175 private static final int PART_COLUMN_ID = 0; 176 private static final int PART_COLUMN_CHARSET = 1; 177 private static final int PART_COLUMN_CONTENT_DISPOSITION = 2; 178 private static final int PART_COLUMN_CONTENT_ID = 3; 179 private static final int PART_COLUMN_CONTENT_LOCATION = 4; 180 private static final int PART_COLUMN_CONTENT_TYPE = 5; 181 private static final int PART_COLUMN_FILENAME = 6; 182 private static final int PART_COLUMN_NAME = 7; 183 private static final int PART_COLUMN_TEXT = 8; 184 185 private static final HashMap<Uri, Integer> MESSAGE_BOX_MAP; 186 // These map are used for convenience in persist() and load(). 187 private static final HashMap<Integer, Integer> CHARSET_COLUMN_INDEX_MAP; 188 private static final HashMap<Integer, Integer> ENCODED_STRING_COLUMN_INDEX_MAP; 189 private static final HashMap<Integer, Integer> TEXT_STRING_COLUMN_INDEX_MAP; 190 private static final HashMap<Integer, Integer> OCTET_COLUMN_INDEX_MAP; 191 private static final HashMap<Integer, Integer> LONG_COLUMN_INDEX_MAP; 192 private static final HashMap<Integer, String> CHARSET_COLUMN_NAME_MAP; 193 private static final HashMap<Integer, String> ENCODED_STRING_COLUMN_NAME_MAP; 194 private static final HashMap<Integer, String> TEXT_STRING_COLUMN_NAME_MAP; 195 private static final HashMap<Integer, String> OCTET_COLUMN_NAME_MAP; 196 private static final HashMap<Integer, String> LONG_COLUMN_NAME_MAP; 197 198 static { 199 MESSAGE_BOX_MAP = new HashMap<Uri, Integer>(); 200 MESSAGE_BOX_MAP.put(Mms.Inbox.CONTENT_URI, Mms.MESSAGE_BOX_INBOX); 201 MESSAGE_BOX_MAP.put(Mms.Sent.CONTENT_URI, Mms.MESSAGE_BOX_SENT); 202 MESSAGE_BOX_MAP.put(Mms.Draft.CONTENT_URI, Mms.MESSAGE_BOX_DRAFTS); 203 MESSAGE_BOX_MAP.put(Mms.Outbox.CONTENT_URI, Mms.MESSAGE_BOX_OUTBOX); 204 205 CHARSET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>(); 206 CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT_CHARSET); 207 CHARSET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT_CHARSET); 208 209 CHARSET_COLUMN_NAME_MAP = new HashMap<Integer, String>(); 210 CHARSET_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT_CHARSET); 211 CHARSET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT_CHARSET); 212 213 // Encoded string field code -> column index/name map. 214 ENCODED_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>(); 215 ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_TEXT, PDU_COLUMN_RETRIEVE_TEXT); 216 ENCODED_STRING_COLUMN_INDEX_MAP.put(PduHeaders.SUBJECT, PDU_COLUMN_SUBJECT); 217 218 ENCODED_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>(); 219 ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_TEXT, Mms.RETRIEVE_TEXT); 220 ENCODED_STRING_COLUMN_NAME_MAP.put(PduHeaders.SUBJECT, Mms.SUBJECT); 221 222 // Text string field code -> column index/name map. 223 TEXT_STRING_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>(); 224 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_LOCATION, PDU_COLUMN_CONTENT_LOCATION); 225 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_TYPE, PDU_COLUMN_CONTENT_TYPE); 226 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_CLASS, PDU_COLUMN_MESSAGE_CLASS); 227 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_ID, PDU_COLUMN_MESSAGE_ID); 228 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.RESPONSE_TEXT, PDU_COLUMN_RESPONSE_TEXT); 229 TEXT_STRING_COLUMN_INDEX_MAP.put(PduHeaders.TRANSACTION_ID, PDU_COLUMN_TRANSACTION_ID); 230 231 TEXT_STRING_COLUMN_NAME_MAP = new HashMap<Integer, String>(); 232 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_LOCATION, Mms.CONTENT_LOCATION); 233 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_TYPE, Mms.CONTENT_TYPE); 234 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_CLASS, Mms.MESSAGE_CLASS); 235 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_ID, Mms.MESSAGE_ID); 236 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.RESPONSE_TEXT, Mms.RESPONSE_TEXT); 237 TEXT_STRING_COLUMN_NAME_MAP.put(PduHeaders.TRANSACTION_ID, Mms.TRANSACTION_ID); 238 239 // Octet field code -> column index/name map. 240 OCTET_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>(); 241 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.CONTENT_CLASS, PDU_COLUMN_CONTENT_CLASS); 242 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_REPORT, PDU_COLUMN_DELIVERY_REPORT); 243 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_TYPE, PDU_COLUMN_MESSAGE_TYPE); 244 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.MMS_VERSION, PDU_COLUMN_MMS_VERSION); 245 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.PRIORITY, PDU_COLUMN_PRIORITY); 246 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_REPORT, PDU_COLUMN_READ_REPORT); 247 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.READ_STATUS, PDU_COLUMN_READ_STATUS); 248 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.REPORT_ALLOWED, PDU_COLUMN_REPORT_ALLOWED); 249 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.RETRIEVE_STATUS, PDU_COLUMN_RETRIEVE_STATUS); 250 OCTET_COLUMN_INDEX_MAP.put(PduHeaders.STATUS, PDU_COLUMN_STATUS); 251 252 OCTET_COLUMN_NAME_MAP = new HashMap<Integer, String>(); 253 OCTET_COLUMN_NAME_MAP.put(PduHeaders.CONTENT_CLASS, Mms.CONTENT_CLASS); 254 OCTET_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_REPORT, Mms.DELIVERY_REPORT); 255 OCTET_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_TYPE, Mms.MESSAGE_TYPE); 256 OCTET_COLUMN_NAME_MAP.put(PduHeaders.MMS_VERSION, Mms.MMS_VERSION); 257 OCTET_COLUMN_NAME_MAP.put(PduHeaders.PRIORITY, Mms.PRIORITY); 258 OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_REPORT, Mms.READ_REPORT); 259 OCTET_COLUMN_NAME_MAP.put(PduHeaders.READ_STATUS, Mms.READ_STATUS); 260 OCTET_COLUMN_NAME_MAP.put(PduHeaders.REPORT_ALLOWED, Mms.REPORT_ALLOWED); 261 OCTET_COLUMN_NAME_MAP.put(PduHeaders.RETRIEVE_STATUS, Mms.RETRIEVE_STATUS); 262 OCTET_COLUMN_NAME_MAP.put(PduHeaders.STATUS, Mms.STATUS); 263 264 // Long field code -> column index/name map. 265 LONG_COLUMN_INDEX_MAP = new HashMap<Integer, Integer>(); 266 LONG_COLUMN_INDEX_MAP.put(PduHeaders.DATE, PDU_COLUMN_DATE); 267 LONG_COLUMN_INDEX_MAP.put(PduHeaders.DELIVERY_TIME, PDU_COLUMN_DELIVERY_TIME); 268 LONG_COLUMN_INDEX_MAP.put(PduHeaders.EXPIRY, PDU_COLUMN_EXPIRY); 269 LONG_COLUMN_INDEX_MAP.put(PduHeaders.MESSAGE_SIZE, PDU_COLUMN_MESSAGE_SIZE); 270 271 LONG_COLUMN_NAME_MAP = new HashMap<Integer, String>(); 272 LONG_COLUMN_NAME_MAP.put(PduHeaders.DATE, Mms.DATE); 273 LONG_COLUMN_NAME_MAP.put(PduHeaders.DELIVERY_TIME, Mms.DELIVERY_TIME); 274 LONG_COLUMN_NAME_MAP.put(PduHeaders.EXPIRY, Mms.EXPIRY); 275 LONG_COLUMN_NAME_MAP.put(PduHeaders.MESSAGE_SIZE, Mms.MESSAGE_SIZE); 276 277 PDU_CACHE_INSTANCE = PduCache.getInstance(); 278 } 279 280 private final Context mContext; 281 private final ContentResolver mContentResolver; 282 private final DrmManagerClient mDrmManagerClient; 283 private final TelephonyManager mTelephonyManager; 284 285 private PduPersister(Context context) { 286 mContext = context; 287 mContentResolver = context.getContentResolver(); 288 mDrmManagerClient = new DrmManagerClient(context); 289 mTelephonyManager = (TelephonyManager)context 290 .getSystemService(Context.TELEPHONY_SERVICE); 291 } 292 293 /** Get(or create if not exist) an instance of PduPersister */ 294 public static PduPersister getPduPersister(Context context) { 295 if ((sPersister == null) || !context.equals(sPersister.mContext)) { 296 sPersister = new PduPersister(context); 297 } 298 299 return sPersister; 300 } 301 302 private void setEncodedStringValueToHeaders( 303 Cursor c, int columnIndex, 304 PduHeaders headers, int mapColumn) { 305 String s = c.getString(columnIndex); 306 if ((s != null) && (s.length() > 0)) { 307 int charsetColumnIndex = CHARSET_COLUMN_INDEX_MAP.get(mapColumn); 308 int charset = c.getInt(charsetColumnIndex); 309 EncodedStringValue value = new EncodedStringValue( 310 charset, getBytes(s)); 311 headers.setEncodedStringValue(value, mapColumn); 312 } 313 } 314 315 private void setTextStringToHeaders( 316 Cursor c, int columnIndex, 317 PduHeaders headers, int mapColumn) { 318 String s = c.getString(columnIndex); 319 if (s != null) { 320 headers.setTextString(getBytes(s), mapColumn); 321 } 322 } 323 324 private void setOctetToHeaders( 325 Cursor c, int columnIndex, 326 PduHeaders headers, int mapColumn) throws InvalidHeaderValueException { 327 if (!c.isNull(columnIndex)) { 328 int b = c.getInt(columnIndex); 329 headers.setOctet(b, mapColumn); 330 } 331 } 332 333 private void setLongToHeaders( 334 Cursor c, int columnIndex, 335 PduHeaders headers, int mapColumn) { 336 if (!c.isNull(columnIndex)) { 337 long l = c.getLong(columnIndex); 338 headers.setLongInteger(l, mapColumn); 339 } 340 } 341 342 private Integer getIntegerFromPartColumn(Cursor c, int columnIndex) { 343 if (!c.isNull(columnIndex)) { 344 return c.getInt(columnIndex); 345 } 346 return null; 347 } 348 349 private byte[] getByteArrayFromPartColumn(Cursor c, int columnIndex) { 350 if (!c.isNull(columnIndex)) { 351 return getBytes(c.getString(columnIndex)); 352 } 353 return null; 354 } 355 356 private PduPart[] loadParts(long msgId) throws MmsException { 357 Cursor c = SqliteWrapper.query(mContext, mContentResolver, 358 Uri.parse("content://mms/" + msgId + "/part"), 359 PART_PROJECTION, null, null, null); 360 361 PduPart[] parts = null; 362 363 try { 364 if ((c == null) || (c.getCount() == 0)) { 365 if (LOCAL_LOGV) { 366 Log.v(TAG, "loadParts(" + msgId + "): no part to load."); 367 } 368 return null; 369 } 370 371 int partCount = c.getCount(); 372 int partIdx = 0; 373 parts = new PduPart[partCount]; 374 while (c.moveToNext()) { 375 PduPart part = new PduPart(); 376 Integer charset = getIntegerFromPartColumn( 377 c, PART_COLUMN_CHARSET); 378 if (charset != null) { 379 part.setCharset(charset); 380 } 381 382 byte[] contentDisposition = getByteArrayFromPartColumn( 383 c, PART_COLUMN_CONTENT_DISPOSITION); 384 if (contentDisposition != null) { 385 part.setContentDisposition(contentDisposition); 386 } 387 388 byte[] contentId = getByteArrayFromPartColumn( 389 c, PART_COLUMN_CONTENT_ID); 390 if (contentId != null) { 391 part.setContentId(contentId); 392 } 393 394 byte[] contentLocation = getByteArrayFromPartColumn( 395 c, PART_COLUMN_CONTENT_LOCATION); 396 if (contentLocation != null) { 397 part.setContentLocation(contentLocation); 398 } 399 400 byte[] contentType = getByteArrayFromPartColumn( 401 c, PART_COLUMN_CONTENT_TYPE); 402 if (contentType != null) { 403 part.setContentType(contentType); 404 } else { 405 throw new MmsException("Content-Type must be set."); 406 } 407 408 byte[] fileName = getByteArrayFromPartColumn( 409 c, PART_COLUMN_FILENAME); 410 if (fileName != null) { 411 part.setFilename(fileName); 412 } 413 414 byte[] name = getByteArrayFromPartColumn( 415 c, PART_COLUMN_NAME); 416 if (name != null) { 417 part.setName(name); 418 } 419 420 // Construct a Uri for this part. 421 long partId = c.getLong(PART_COLUMN_ID); 422 Uri partURI = Uri.parse("content://mms/part/" + partId); 423 part.setDataUri(partURI); 424 425 // For images/audio/video, we won't keep their data in Part 426 // because their renderer accept Uri as source. 427 String type = toIsoString(contentType); 428 if (!ContentType.isImageType(type) 429 && !ContentType.isAudioType(type) 430 && !ContentType.isVideoType(type)) { 431 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 432 InputStream is = null; 433 434 // Store simple string values directly in the database instead of an 435 // external file. This makes the text searchable and retrieval slightly 436 // faster. 437 if (ContentType.TEXT_PLAIN.equals(type) || ContentType.APP_SMIL.equals(type) 438 || ContentType.TEXT_HTML.equals(type)) { 439 String text = c.getString(PART_COLUMN_TEXT); 440 byte [] blob = new EncodedStringValue(text != null ? text : "") 441 .getTextString(); 442 baos.write(blob, 0, blob.length); 443 } else { 444 445 try { 446 is = mContentResolver.openInputStream(partURI); 447 448 byte[] buffer = new byte[256]; 449 int len = is.read(buffer); 450 while (len >= 0) { 451 baos.write(buffer, 0, len); 452 len = is.read(buffer); 453 } 454 } catch (IOException e) { 455 Log.e(TAG, "Failed to load part data", e); 456 c.close(); 457 throw new MmsException(e); 458 } finally { 459 if (is != null) { 460 try { 461 is.close(); 462 } catch (IOException e) { 463 Log.e(TAG, "Failed to close stream", e); 464 } // Ignore 465 } 466 } 467 } 468 part.setData(baos.toByteArray()); 469 } 470 parts[partIdx++] = part; 471 } 472 } finally { 473 if (c != null) { 474 c.close(); 475 } 476 } 477 478 return parts; 479 } 480 481 private void loadAddress(long msgId, PduHeaders headers) { 482 Cursor c = SqliteWrapper.query(mContext, mContentResolver, 483 Uri.parse("content://mms/" + msgId + "/addr"), 484 new String[] { Addr.ADDRESS, Addr.CHARSET, Addr.TYPE }, 485 null, null, null); 486 487 if (c != null) { 488 try { 489 while (c.moveToNext()) { 490 String addr = c.getString(0); 491 if (!TextUtils.isEmpty(addr)) { 492 int addrType = c.getInt(2); 493 switch (addrType) { 494 case PduHeaders.FROM: 495 headers.setEncodedStringValue( 496 new EncodedStringValue(c.getInt(1), getBytes(addr)), 497 addrType); 498 break; 499 case PduHeaders.TO: 500 case PduHeaders.CC: 501 case PduHeaders.BCC: 502 headers.appendEncodedStringValue( 503 new EncodedStringValue(c.getInt(1), getBytes(addr)), 504 addrType); 505 break; 506 default: 507 Log.e(TAG, "Unknown address type: " + addrType); 508 break; 509 } 510 } 511 } 512 } finally { 513 c.close(); 514 } 515 } 516 } 517 518 /** 519 * Load a PDU from storage by given Uri. 520 * 521 * @param uri The Uri of the PDU to be loaded. 522 * @return A generic PDU object, it may be cast to dedicated PDU. 523 * @throws MmsException Failed to load some fields of a PDU. 524 */ 525 public GenericPdu load(Uri uri) throws MmsException { 526 GenericPdu pdu = null; 527 PduCacheEntry cacheEntry = null; 528 int msgBox = 0; 529 long threadId = -1; 530 try { 531 synchronized(PDU_CACHE_INSTANCE) { 532 if (PDU_CACHE_INSTANCE.isUpdating(uri)) { 533 if (LOCAL_LOGV) { 534 Log.v(TAG, "load: " + uri + " blocked by isUpdating()"); 535 } 536 try { 537 PDU_CACHE_INSTANCE.wait(); 538 } catch (InterruptedException e) { 539 Log.e(TAG, "load: ", e); 540 } 541 cacheEntry = PDU_CACHE_INSTANCE.get(uri); 542 if (cacheEntry != null) { 543 return cacheEntry.getPdu(); 544 } 545 } 546 // Tell the cache to indicate to other callers that this item 547 // is currently being updated. 548 PDU_CACHE_INSTANCE.setUpdating(uri, true); 549 } 550 551 Cursor c = SqliteWrapper.query(mContext, mContentResolver, uri, 552 PDU_PROJECTION, null, null, null); 553 PduHeaders headers = new PduHeaders(); 554 Set<Entry<Integer, Integer>> set; 555 long msgId = ContentUris.parseId(uri); 556 557 try { 558 if ((c == null) || (c.getCount() != 1) || !c.moveToFirst()) { 559 throw new MmsException("Bad uri: " + uri); 560 } 561 562 msgBox = c.getInt(PDU_COLUMN_MESSAGE_BOX); 563 threadId = c.getLong(PDU_COLUMN_THREAD_ID); 564 565 set = ENCODED_STRING_COLUMN_INDEX_MAP.entrySet(); 566 for (Entry<Integer, Integer> e : set) { 567 setEncodedStringValueToHeaders( 568 c, e.getValue(), headers, e.getKey()); 569 } 570 571 set = TEXT_STRING_COLUMN_INDEX_MAP.entrySet(); 572 for (Entry<Integer, Integer> e : set) { 573 setTextStringToHeaders( 574 c, e.getValue(), headers, e.getKey()); 575 } 576 577 set = OCTET_COLUMN_INDEX_MAP.entrySet(); 578 for (Entry<Integer, Integer> e : set) { 579 setOctetToHeaders( 580 c, e.getValue(), headers, e.getKey()); 581 } 582 583 set = LONG_COLUMN_INDEX_MAP.entrySet(); 584 for (Entry<Integer, Integer> e : set) { 585 setLongToHeaders( 586 c, e.getValue(), headers, e.getKey()); 587 } 588 } finally { 589 if (c != null) { 590 c.close(); 591 } 592 } 593 594 // Check whether 'msgId' has been assigned a valid value. 595 if (msgId == -1L) { 596 throw new MmsException("Error! ID of the message: -1."); 597 } 598 599 // Load address information of the MM. 600 loadAddress(msgId, headers); 601 602 int msgType = headers.getOctet(PduHeaders.MESSAGE_TYPE); 603 PduBody body = new PduBody(); 604 605 // For PDU which type is M_retrieve.conf or Send.req, we should 606 // load multiparts and put them into the body of the PDU. 607 if ((msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) 608 || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { 609 PduPart[] parts = loadParts(msgId); 610 if (parts != null) { 611 int partsNum = parts.length; 612 for (int i = 0; i < partsNum; i++) { 613 body.addPart(parts[i]); 614 } 615 } 616 } 617 618 switch (msgType) { 619 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 620 pdu = new NotificationInd(headers); 621 break; 622 case PduHeaders.MESSAGE_TYPE_DELIVERY_IND: 623 pdu = new DeliveryInd(headers); 624 break; 625 case PduHeaders.MESSAGE_TYPE_READ_ORIG_IND: 626 pdu = new ReadOrigInd(headers); 627 break; 628 case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: 629 pdu = new RetrieveConf(headers, body); 630 break; 631 case PduHeaders.MESSAGE_TYPE_SEND_REQ: 632 pdu = new SendReq(headers, body); 633 break; 634 case PduHeaders.MESSAGE_TYPE_ACKNOWLEDGE_IND: 635 pdu = new AcknowledgeInd(headers); 636 break; 637 case PduHeaders.MESSAGE_TYPE_NOTIFYRESP_IND: 638 pdu = new NotifyRespInd(headers); 639 break; 640 case PduHeaders.MESSAGE_TYPE_READ_REC_IND: 641 pdu = new ReadRecInd(headers); 642 break; 643 case PduHeaders.MESSAGE_TYPE_SEND_CONF: 644 case PduHeaders.MESSAGE_TYPE_FORWARD_REQ: 645 case PduHeaders.MESSAGE_TYPE_FORWARD_CONF: 646 case PduHeaders.MESSAGE_TYPE_MBOX_STORE_REQ: 647 case PduHeaders.MESSAGE_TYPE_MBOX_STORE_CONF: 648 case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_REQ: 649 case PduHeaders.MESSAGE_TYPE_MBOX_VIEW_CONF: 650 case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_REQ: 651 case PduHeaders.MESSAGE_TYPE_MBOX_UPLOAD_CONF: 652 case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_REQ: 653 case PduHeaders.MESSAGE_TYPE_MBOX_DELETE_CONF: 654 case PduHeaders.MESSAGE_TYPE_MBOX_DESCR: 655 case PduHeaders.MESSAGE_TYPE_DELETE_REQ: 656 case PduHeaders.MESSAGE_TYPE_DELETE_CONF: 657 case PduHeaders.MESSAGE_TYPE_CANCEL_REQ: 658 case PduHeaders.MESSAGE_TYPE_CANCEL_CONF: 659 throw new MmsException( 660 "Unsupported PDU type: " + Integer.toHexString(msgType)); 661 662 default: 663 throw new MmsException( 664 "Unrecognized PDU type: " + Integer.toHexString(msgType)); 665 } 666 } finally { 667 synchronized(PDU_CACHE_INSTANCE) { 668 if (pdu != null) { 669 assert(PDU_CACHE_INSTANCE.get(uri) == null); 670 // Update the cache entry with the real info 671 cacheEntry = new PduCacheEntry(pdu, msgBox, threadId); 672 PDU_CACHE_INSTANCE.put(uri, cacheEntry); 673 } 674 PDU_CACHE_INSTANCE.setUpdating(uri, false); 675 PDU_CACHE_INSTANCE.notifyAll(); // tell anybody waiting on this entry to go ahead 676 } 677 } 678 return pdu; 679 } 680 681 private void persistAddress( 682 long msgId, int type, EncodedStringValue[] array) { 683 ContentValues values = new ContentValues(3); 684 685 for (EncodedStringValue addr : array) { 686 values.clear(); // Clear all values first. 687 values.put(Addr.ADDRESS, toIsoString(addr.getTextString())); 688 values.put(Addr.CHARSET, addr.getCharacterSet()); 689 values.put(Addr.TYPE, type); 690 691 Uri uri = Uri.parse("content://mms/" + msgId + "/addr"); 692 SqliteWrapper.insert(mContext, mContentResolver, uri, values); 693 } 694 } 695 696 private static String getPartContentType(PduPart part) { 697 return part.getContentType() == null ? null : toIsoString(part.getContentType()); 698 } 699 700 public Uri persistPart(PduPart part, long msgId) 701 throws MmsException { 702 Uri uri = Uri.parse("content://mms/" + msgId + "/part"); 703 ContentValues values = new ContentValues(8); 704 705 int charset = part.getCharset(); 706 if (charset != 0 ) { 707 values.put(Part.CHARSET, charset); 708 } 709 710 String contentType = getPartContentType(part); 711 if (contentType != null) { 712 // There is no "image/jpg" in Android (and it's an invalid mimetype). 713 // Change it to "image/jpeg" 714 if (ContentType.IMAGE_JPG.equals(contentType)) { 715 contentType = ContentType.IMAGE_JPEG; 716 } 717 718 values.put(Part.CONTENT_TYPE, contentType); 719 // To ensure the SMIL part is always the first part. 720 if (ContentType.APP_SMIL.equals(contentType)) { 721 values.put(Part.SEQ, -1); 722 } 723 } else { 724 throw new MmsException("MIME type of the part must be set."); 725 } 726 727 if (part.getFilename() != null) { 728 String fileName = new String(part.getFilename()); 729 values.put(Part.FILENAME, fileName); 730 } 731 732 if (part.getName() != null) { 733 String name = new String(part.getName()); 734 values.put(Part.NAME, name); 735 } 736 737 Object value = null; 738 if (part.getContentDisposition() != null) { 739 value = toIsoString(part.getContentDisposition()); 740 values.put(Part.CONTENT_DISPOSITION, (String) value); 741 } 742 743 if (part.getContentId() != null) { 744 value = toIsoString(part.getContentId()); 745 values.put(Part.CONTENT_ID, (String) value); 746 } 747 748 if (part.getContentLocation() != null) { 749 value = toIsoString(part.getContentLocation()); 750 values.put(Part.CONTENT_LOCATION, (String) value); 751 } 752 753 Uri res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); 754 if (res == null) { 755 throw new MmsException("Failed to persist part, return null."); 756 } 757 758 persistData(part, res, contentType); 759 // After successfully store the data, we should update 760 // the dataUri of the part. 761 part.setDataUri(res); 762 763 return res; 764 } 765 766 /** 767 * Save data of the part into storage. The source data may be given 768 * by a byte[] or a Uri. If it's a byte[], directly save it 769 * into storage, otherwise load source data from the dataUri and then 770 * save it. If the data is an image, we may scale down it according 771 * to user preference. 772 * 773 * @param part The PDU part which contains data to be saved. 774 * @param uri The URI of the part. 775 * @param contentType The MIME type of the part. 776 * @throws MmsException Cannot find source data or error occurred 777 * while saving the data. 778 */ 779 private void persistData(PduPart part, Uri uri, 780 String contentType) 781 throws MmsException { 782 OutputStream os = null; 783 InputStream is = null; 784 DrmConvertSession drmConvertSession = null; 785 Uri dataUri = null; 786 String path = null; 787 788 try { 789 byte[] data = part.getData(); 790 if (ContentType.TEXT_PLAIN.equals(contentType) 791 || ContentType.APP_SMIL.equals(contentType) 792 || ContentType.TEXT_HTML.equals(contentType)) { 793 ContentValues cv = new ContentValues(); 794 cv.put(Telephony.Mms.Part.TEXT, new EncodedStringValue(data).getString()); 795 if (mContentResolver.update(uri, cv, null, null) != 1) { 796 throw new MmsException("unable to update " + uri.toString()); 797 } 798 } else { 799 boolean isDrm = DownloadDrmHelper.isDrmConvertNeeded(contentType); 800 if (isDrm) { 801 if (uri != null) { 802 try { 803 path = convertUriToPath(mContext, uri); 804 if (LOCAL_LOGV) { 805 Log.v(TAG, "drm uri: " + uri + " path: " + path); 806 } 807 File f = new File(path); 808 long len = f.length(); 809 if (LOCAL_LOGV) { 810 Log.v(TAG, "drm path: " + path + " len: " + len); 811 } 812 if (len > 0) { 813 // we're not going to re-persist and re-encrypt an already 814 // converted drm file 815 return; 816 } 817 } catch (Exception e) { 818 Log.e(TAG, "Can't get file info for: " + part.getDataUri(), e); 819 } 820 } 821 // We haven't converted the file yet, start the conversion 822 drmConvertSession = DrmConvertSession.open(mContext, contentType); 823 if (drmConvertSession == null) { 824 throw new MmsException("Mimetype " + contentType + 825 " can not be converted."); 826 } 827 } 828 os = mContentResolver.openOutputStream(uri); 829 if (data == null) { 830 dataUri = part.getDataUri(); 831 if ((dataUri == null) || (dataUri == uri)) { 832 Log.w(TAG, "Can't find data for this part."); 833 return; 834 } 835 is = mContentResolver.openInputStream(dataUri); 836 837 if (LOCAL_LOGV) { 838 Log.v(TAG, "Saving data to: " + uri); 839 } 840 841 byte[] buffer = new byte[8192]; 842 for (int len = 0; (len = is.read(buffer)) != -1; ) { 843 if (!isDrm) { 844 os.write(buffer, 0, len); 845 } else { 846 byte[] convertedData = drmConvertSession.convert(buffer, len); 847 if (convertedData != null) { 848 os.write(convertedData, 0, convertedData.length); 849 } else { 850 throw new MmsException("Error converting drm data."); 851 } 852 } 853 } 854 } else { 855 if (LOCAL_LOGV) { 856 Log.v(TAG, "Saving data to: " + uri); 857 } 858 if (!isDrm) { 859 os.write(data); 860 } else { 861 dataUri = uri; 862 byte[] convertedData = drmConvertSession.convert(data, data.length); 863 if (convertedData != null) { 864 os.write(convertedData, 0, convertedData.length); 865 } else { 866 throw new MmsException("Error converting drm data."); 867 } 868 } 869 } 870 } 871 } catch (FileNotFoundException e) { 872 Log.e(TAG, "Failed to open Input/Output stream.", e); 873 throw new MmsException(e); 874 } catch (IOException e) { 875 Log.e(TAG, "Failed to read/write data.", e); 876 throw new MmsException(e); 877 } finally { 878 if (os != null) { 879 try { 880 os.close(); 881 } catch (IOException e) { 882 Log.e(TAG, "IOException while closing: " + os, e); 883 } // Ignore 884 } 885 if (is != null) { 886 try { 887 is.close(); 888 } catch (IOException e) { 889 Log.e(TAG, "IOException while closing: " + is, e); 890 } // Ignore 891 } 892 if (drmConvertSession != null) { 893 drmConvertSession.close(path); 894 895 // Reset the permissions on the encrypted part file so everyone has only read 896 // permission. 897 File f = new File(path); 898 ContentValues values = new ContentValues(0); 899 SqliteWrapper.update(mContext, mContentResolver, 900 Uri.parse("content://mms/resetFilePerm/" + f.getName()), 901 values, null, null); 902 } 903 } 904 } 905 906 /** 907 * This method expects uri in the following format 908 * content://media/<table_name>/<row_index> (or) 909 * file://sdcard/test.mp4 910 * http://test.com/test.mp4 911 * 912 * Here <table_name> shall be "video" or "audio" or "images" 913 * <row_index> the index of the content in given table 914 */ 915 static public String convertUriToPath(Context context, Uri uri) { 916 String path = null; 917 if (null != uri) { 918 String scheme = uri.getScheme(); 919 if (null == scheme || scheme.equals("") || 920 scheme.equals(ContentResolver.SCHEME_FILE)) { 921 path = uri.getPath(); 922 923 } else if (scheme.equals("http")) { 924 path = uri.toString(); 925 926 } else if (scheme.equals(ContentResolver.SCHEME_CONTENT)) { 927 String[] projection = new String[] {MediaStore.MediaColumns.DATA}; 928 Cursor cursor = null; 929 try { 930 cursor = context.getContentResolver().query(uri, projection, null, 931 null, null); 932 if (null == cursor || 0 == cursor.getCount() || !cursor.moveToFirst()) { 933 throw new IllegalArgumentException("Given Uri could not be found" + 934 " in media store"); 935 } 936 int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); 937 path = cursor.getString(pathIndex); 938 } catch (SQLiteException e) { 939 throw new IllegalArgumentException("Given Uri is not formatted in a way " + 940 "so that it can be found in media store."); 941 } finally { 942 if (null != cursor) { 943 cursor.close(); 944 } 945 } 946 } else { 947 throw new IllegalArgumentException("Given Uri scheme is not supported"); 948 } 949 } 950 return path; 951 } 952 953 private void updateAddress( 954 long msgId, int type, EncodedStringValue[] array) { 955 // Delete old address information and then insert new ones. 956 SqliteWrapper.delete(mContext, mContentResolver, 957 Uri.parse("content://mms/" + msgId + "/addr"), 958 Addr.TYPE + "=" + type, null); 959 960 persistAddress(msgId, type, array); 961 } 962 963 /** 964 * Update headers of a SendReq. 965 * 966 * @param uri The PDU which need to be updated. 967 * @param pdu New headers. 968 * @throws MmsException Bad URI or updating failed. 969 */ 970 public void updateHeaders(Uri uri, SendReq sendReq) { 971 synchronized(PDU_CACHE_INSTANCE) { 972 // If the cache item is getting updated, wait until it's done updating before 973 // purging it. 974 if (PDU_CACHE_INSTANCE.isUpdating(uri)) { 975 if (LOCAL_LOGV) { 976 Log.v(TAG, "updateHeaders: " + uri + " blocked by isUpdating()"); 977 } 978 try { 979 PDU_CACHE_INSTANCE.wait(); 980 } catch (InterruptedException e) { 981 Log.e(TAG, "updateHeaders: ", e); 982 } 983 } 984 } 985 PDU_CACHE_INSTANCE.purge(uri); 986 987 ContentValues values = new ContentValues(10); 988 byte[] contentType = sendReq.getContentType(); 989 if (contentType != null) { 990 values.put(Mms.CONTENT_TYPE, toIsoString(contentType)); 991 } 992 993 long date = sendReq.getDate(); 994 if (date != -1) { 995 values.put(Mms.DATE, date); 996 } 997 998 int deliveryReport = sendReq.getDeliveryReport(); 999 if (deliveryReport != 0) { 1000 values.put(Mms.DELIVERY_REPORT, deliveryReport); 1001 } 1002 1003 long expiry = sendReq.getExpiry(); 1004 if (expiry != -1) { 1005 values.put(Mms.EXPIRY, expiry); 1006 } 1007 1008 byte[] msgClass = sendReq.getMessageClass(); 1009 if (msgClass != null) { 1010 values.put(Mms.MESSAGE_CLASS, toIsoString(msgClass)); 1011 } 1012 1013 int priority = sendReq.getPriority(); 1014 if (priority != 0) { 1015 values.put(Mms.PRIORITY, priority); 1016 } 1017 1018 int readReport = sendReq.getReadReport(); 1019 if (readReport != 0) { 1020 values.put(Mms.READ_REPORT, readReport); 1021 } 1022 1023 byte[] transId = sendReq.getTransactionId(); 1024 if (transId != null) { 1025 values.put(Mms.TRANSACTION_ID, toIsoString(transId)); 1026 } 1027 1028 EncodedStringValue subject = sendReq.getSubject(); 1029 if (subject != null) { 1030 values.put(Mms.SUBJECT, toIsoString(subject.getTextString())); 1031 values.put(Mms.SUBJECT_CHARSET, subject.getCharacterSet()); 1032 } else { 1033 values.put(Mms.SUBJECT, ""); 1034 } 1035 1036 long messageSize = sendReq.getMessageSize(); 1037 if (messageSize > 0) { 1038 values.put(Mms.MESSAGE_SIZE, messageSize); 1039 } 1040 1041 PduHeaders headers = sendReq.getPduHeaders(); 1042 HashSet<String> recipients = new HashSet<String>(); 1043 for (int addrType : ADDRESS_FIELDS) { 1044 EncodedStringValue[] array = null; 1045 if (addrType == PduHeaders.FROM) { 1046 EncodedStringValue v = headers.getEncodedStringValue(addrType); 1047 if (v != null) { 1048 array = new EncodedStringValue[1]; 1049 array[0] = v; 1050 } 1051 } else { 1052 array = headers.getEncodedStringValues(addrType); 1053 } 1054 1055 if (array != null) { 1056 long msgId = ContentUris.parseId(uri); 1057 updateAddress(msgId, addrType, array); 1058 if (addrType == PduHeaders.TO) { 1059 for (EncodedStringValue v : array) { 1060 if (v != null) { 1061 recipients.add(v.getString()); 1062 } 1063 } 1064 } 1065 } 1066 } 1067 if (!recipients.isEmpty()) { 1068 long threadId = Threads.getOrCreateThreadId(mContext, recipients); 1069 values.put(Mms.THREAD_ID, threadId); 1070 } 1071 1072 SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); 1073 } 1074 1075 private void updatePart(Uri uri, PduPart part) throws MmsException { 1076 ContentValues values = new ContentValues(7); 1077 1078 int charset = part.getCharset(); 1079 if (charset != 0 ) { 1080 values.put(Part.CHARSET, charset); 1081 } 1082 1083 String contentType = null; 1084 if (part.getContentType() != null) { 1085 contentType = toIsoString(part.getContentType()); 1086 values.put(Part.CONTENT_TYPE, contentType); 1087 } else { 1088 throw new MmsException("MIME type of the part must be set."); 1089 } 1090 1091 if (part.getFilename() != null) { 1092 String fileName = new String(part.getFilename()); 1093 values.put(Part.FILENAME, fileName); 1094 } 1095 1096 if (part.getName() != null) { 1097 String name = new String(part.getName()); 1098 values.put(Part.NAME, name); 1099 } 1100 1101 Object value = null; 1102 if (part.getContentDisposition() != null) { 1103 value = toIsoString(part.getContentDisposition()); 1104 values.put(Part.CONTENT_DISPOSITION, (String) value); 1105 } 1106 1107 if (part.getContentId() != null) { 1108 value = toIsoString(part.getContentId()); 1109 values.put(Part.CONTENT_ID, (String) value); 1110 } 1111 1112 if (part.getContentLocation() != null) { 1113 value = toIsoString(part.getContentLocation()); 1114 values.put(Part.CONTENT_LOCATION, (String) value); 1115 } 1116 1117 SqliteWrapper.update(mContext, mContentResolver, uri, values, null, null); 1118 1119 // Only update the data when: 1120 // 1. New binary data supplied or 1121 // 2. The Uri of the part is different from the current one. 1122 if ((part.getData() != null) 1123 || (uri != part.getDataUri())) { 1124 persistData(part, uri, contentType); 1125 } 1126 } 1127 1128 /** 1129 * Update all parts of a PDU. 1130 * 1131 * @param uri The PDU which need to be updated. 1132 * @param body New message body of the PDU. 1133 * @throws MmsException Bad URI or updating failed. 1134 */ 1135 public void updateParts(Uri uri, PduBody body) 1136 throws MmsException { 1137 try { 1138 PduCacheEntry cacheEntry; 1139 synchronized(PDU_CACHE_INSTANCE) { 1140 if (PDU_CACHE_INSTANCE.isUpdating(uri)) { 1141 if (LOCAL_LOGV) { 1142 Log.v(TAG, "updateParts: " + uri + " blocked by isUpdating()"); 1143 } 1144 try { 1145 PDU_CACHE_INSTANCE.wait(); 1146 } catch (InterruptedException e) { 1147 Log.e(TAG, "updateParts: ", e); 1148 } 1149 cacheEntry = PDU_CACHE_INSTANCE.get(uri); 1150 if (cacheEntry != null) { 1151 ((MultimediaMessagePdu) cacheEntry.getPdu()).setBody(body); 1152 } 1153 } 1154 // Tell the cache to indicate to other callers that this item 1155 // is currently being updated. 1156 PDU_CACHE_INSTANCE.setUpdating(uri, true); 1157 } 1158 1159 ArrayList<PduPart> toBeCreated = new ArrayList<PduPart>(); 1160 HashMap<Uri, PduPart> toBeUpdated = new HashMap<Uri, PduPart>(); 1161 1162 int partsNum = body.getPartsNum(); 1163 StringBuilder filter = new StringBuilder().append('('); 1164 for (int i = 0; i < partsNum; i++) { 1165 PduPart part = body.getPart(i); 1166 Uri partUri = part.getDataUri(); 1167 if ((partUri == null) || !partUri.getAuthority().startsWith("mms")) { 1168 toBeCreated.add(part); 1169 } else { 1170 toBeUpdated.put(partUri, part); 1171 1172 // Don't use 'i > 0' to determine whether we should append 1173 // 'AND' since 'i = 0' may be skipped in another branch. 1174 if (filter.length() > 1) { 1175 filter.append(" AND "); 1176 } 1177 1178 filter.append(Part._ID); 1179 filter.append("!="); 1180 DatabaseUtils.appendEscapedSQLString(filter, partUri.getLastPathSegment()); 1181 } 1182 } 1183 filter.append(')'); 1184 1185 long msgId = ContentUris.parseId(uri); 1186 1187 // Remove the parts which doesn't exist anymore. 1188 SqliteWrapper.delete(mContext, mContentResolver, 1189 Uri.parse(Mms.CONTENT_URI + "/" + msgId + "/part"), 1190 filter.length() > 2 ? filter.toString() : null, null); 1191 1192 // Create new parts which didn't exist before. 1193 for (PduPart part : toBeCreated) { 1194 persistPart(part, msgId); 1195 } 1196 1197 // Update the modified parts. 1198 for (Map.Entry<Uri, PduPart> e : toBeUpdated.entrySet()) { 1199 updatePart(e.getKey(), e.getValue()); 1200 } 1201 } finally { 1202 synchronized(PDU_CACHE_INSTANCE) { 1203 PDU_CACHE_INSTANCE.setUpdating(uri, false); 1204 PDU_CACHE_INSTANCE.notifyAll(); 1205 } 1206 } 1207 } 1208 1209 /** 1210 * Persist a PDU object to specific location in the storage. 1211 * 1212 * @param pdu The PDU object to be stored. 1213 * @param uri Where to store the given PDU object. 1214 * @param createThreadId if true, this function may create a thread id for the recipients 1215 * @param groupMmsEnabled if true, all of the recipients addressed in the PDU will be used 1216 * to create the associated thread. When false, only the sender will be used in finding or 1217 * creating the appropriate thread or conversation. 1218 * @return A Uri which can be used to access the stored PDU. 1219 */ 1220 1221 public Uri persist(GenericPdu pdu, Uri uri, boolean createThreadId, boolean groupMmsEnabled) 1222 throws MmsException { 1223 if (uri == null) { 1224 throw new MmsException("Uri may not be null."); 1225 } 1226 long msgId = -1; 1227 try { 1228 msgId = ContentUris.parseId(uri); 1229 } catch (NumberFormatException e) { 1230 // the uri ends with "inbox" or something else like that 1231 } 1232 boolean existingUri = msgId != -1; 1233 1234 if (!existingUri && MESSAGE_BOX_MAP.get(uri) == null) { 1235 throw new MmsException( 1236 "Bad destination, must be one of " 1237 + "content://mms/inbox, content://mms/sent, " 1238 + "content://mms/drafts, content://mms/outbox, " 1239 + "content://mms/temp."); 1240 } 1241 synchronized(PDU_CACHE_INSTANCE) { 1242 // If the cache item is getting updated, wait until it's done updating before 1243 // purging it. 1244 if (PDU_CACHE_INSTANCE.isUpdating(uri)) { 1245 if (LOCAL_LOGV) { 1246 Log.v(TAG, "persist: " + uri + " blocked by isUpdating()"); 1247 } 1248 try { 1249 PDU_CACHE_INSTANCE.wait(); 1250 } catch (InterruptedException e) { 1251 Log.e(TAG, "persist1: ", e); 1252 } 1253 } 1254 } 1255 PDU_CACHE_INSTANCE.purge(uri); 1256 1257 PduHeaders header = pdu.getPduHeaders(); 1258 PduBody body = null; 1259 ContentValues values = new ContentValues(); 1260 Set<Entry<Integer, String>> set; 1261 1262 set = ENCODED_STRING_COLUMN_NAME_MAP.entrySet(); 1263 for (Entry<Integer, String> e : set) { 1264 int field = e.getKey(); 1265 EncodedStringValue encodedString = header.getEncodedStringValue(field); 1266 if (encodedString != null) { 1267 String charsetColumn = CHARSET_COLUMN_NAME_MAP.get(field); 1268 values.put(e.getValue(), toIsoString(encodedString.getTextString())); 1269 values.put(charsetColumn, encodedString.getCharacterSet()); 1270 } 1271 } 1272 1273 set = TEXT_STRING_COLUMN_NAME_MAP.entrySet(); 1274 for (Entry<Integer, String> e : set){ 1275 byte[] text = header.getTextString(e.getKey()); 1276 if (text != null) { 1277 values.put(e.getValue(), toIsoString(text)); 1278 } 1279 } 1280 1281 set = OCTET_COLUMN_NAME_MAP.entrySet(); 1282 for (Entry<Integer, String> e : set){ 1283 int b = header.getOctet(e.getKey()); 1284 if (b != 0) { 1285 values.put(e.getValue(), b); 1286 } 1287 } 1288 1289 set = LONG_COLUMN_NAME_MAP.entrySet(); 1290 for (Entry<Integer, String> e : set){ 1291 long l = header.getLongInteger(e.getKey()); 1292 if (l != -1L) { 1293 values.put(e.getValue(), l); 1294 } 1295 } 1296 1297 HashMap<Integer, EncodedStringValue[]> addressMap = 1298 new HashMap<Integer, EncodedStringValue[]>(ADDRESS_FIELDS.length); 1299 // Save address information. 1300 for (int addrType : ADDRESS_FIELDS) { 1301 EncodedStringValue[] array = null; 1302 if (addrType == PduHeaders.FROM) { 1303 EncodedStringValue v = header.getEncodedStringValue(addrType); 1304 if (v != null) { 1305 array = new EncodedStringValue[1]; 1306 array[0] = v; 1307 } 1308 } else { 1309 array = header.getEncodedStringValues(addrType); 1310 } 1311 addressMap.put(addrType, array); 1312 } 1313 1314 HashSet<String> recipients = new HashSet<String>(); 1315 int msgType = pdu.getMessageType(); 1316 // Here we only allocate thread ID for M-Notification.ind, 1317 // M-Retrieve.conf and M-Send.req. 1318 // Some of other PDU types may be allocated a thread ID outside 1319 // this scope. 1320 if ((msgType == PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND) 1321 || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF) 1322 || (msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)) { 1323 switch (msgType) { 1324 case PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND: 1325 case PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF: 1326 // For received messages, we want to associate this message with the thread 1327 // composed of all the recipients. This includes the person who sent the 1328 // message or the FROM field in addition to the other people the message 1329 // was addressed to or the TO field. 1330 loadRecipients(PduHeaders.FROM, recipients, addressMap, false); 1331 if (groupMmsEnabled) { 1332 loadRecipients(PduHeaders.TO, recipients, addressMap, true); 1333 } 1334 break; 1335 case PduHeaders.MESSAGE_TYPE_SEND_REQ: 1336 loadRecipients(PduHeaders.TO, recipients, addressMap, false); 1337 break; 1338 } 1339 1340 if (createThreadId && !recipients.isEmpty()) { 1341 // Given all the recipients associated with this message, find (or create) the 1342 // correct thread. 1343 long threadId = Threads.getOrCreateThreadId(mContext, recipients); 1344 values.put(Mms.THREAD_ID, threadId); 1345 } 1346 } 1347 1348 // Save parts first to avoid inconsistent message is loaded 1349 // while saving the parts. 1350 long dummyId = System.currentTimeMillis(); // Dummy ID of the msg. 1351 1352 // Figure out if this PDU is a text-only message 1353 boolean textOnly = true; 1354 1355 // Get body if the PDU is a RetrieveConf or SendReq. 1356 if (pdu instanceof MultimediaMessagePdu) { 1357 body = ((MultimediaMessagePdu) pdu).getBody(); 1358 // Start saving parts if necessary. 1359 if (body != null) { 1360 int partsNum = body.getPartsNum(); 1361 if (partsNum > 2) { 1362 // For a text-only message there will be two parts: 1-the SMIL, 2-the text. 1363 // Down a few lines below we're checking to make sure we've only got SMIL or 1364 // text. We also have to check then we don't have more than two parts. 1365 // Otherwise, a slideshow with two text slides would be marked as textOnly. 1366 textOnly = false; 1367 } 1368 for (int i = 0; i < partsNum; i++) { 1369 PduPart part = body.getPart(i); 1370 persistPart(part, dummyId); 1371 1372 // If we've got anything besides text/plain or SMIL part, then we've got 1373 // an mms message with some other type of attachment. 1374 String contentType = getPartContentType(part); 1375 if (contentType != null && !ContentType.APP_SMIL.equals(contentType) 1376 && !ContentType.TEXT_PLAIN.equals(contentType)) { 1377 textOnly = false; 1378 } 1379 } 1380 } 1381 } 1382 // Record whether this mms message is a simple plain text or not. This is a hint for the 1383 // UI. 1384 values.put(Mms.TEXT_ONLY, textOnly ? 1 : 0); 1385 1386 Uri res = null; 1387 if (existingUri) { 1388 res = uri; 1389 SqliteWrapper.update(mContext, mContentResolver, res, values, null, null); 1390 } else { 1391 res = SqliteWrapper.insert(mContext, mContentResolver, uri, values); 1392 if (res == null) { 1393 throw new MmsException("persist() failed: return null."); 1394 } 1395 // Get the real ID of the PDU and update all parts which were 1396 // saved with the dummy ID. 1397 msgId = ContentUris.parseId(res); 1398 } 1399 1400 values = new ContentValues(1); 1401 values.put(Part.MSG_ID, msgId); 1402 SqliteWrapper.update(mContext, mContentResolver, 1403 Uri.parse("content://mms/" + dummyId + "/part"), 1404 values, null, null); 1405 // We should return the longest URI of the persisted PDU, for 1406 // example, if input URI is "content://mms/inbox" and the _ID of 1407 // persisted PDU is '8', we should return "content://mms/inbox/8" 1408 // instead of "content://mms/8". 1409 // FIXME: Should the MmsProvider be responsible for this??? 1410 if (!existingUri) { 1411 res = Uri.parse(uri + "/" + msgId); 1412 } 1413 1414 // Save address information. 1415 for (int addrType : ADDRESS_FIELDS) { 1416 EncodedStringValue[] array = addressMap.get(addrType); 1417 if (array != null) { 1418 persistAddress(msgId, addrType, array); 1419 } 1420 } 1421 1422 return res; 1423 } 1424 1425 /** 1426 * For a given address type, extract the recipients from the headers. 1427 * 1428 * @param addressType can be PduHeaders.FROM or PduHeaders.TO 1429 * @param recipients a HashSet that is loaded with the recipients from the FROM or TO headers 1430 * @param addressMap a HashMap of the addresses from the ADDRESS_FIELDS header 1431 * @param excludeMyNumber if true, the number of this phone will be excluded from recipients 1432 */ 1433 private void loadRecipients(int addressType, HashSet<String> recipients, 1434 HashMap<Integer, EncodedStringValue[]> addressMap, boolean excludeMyNumber) { 1435 EncodedStringValue[] array = addressMap.get(addressType); 1436 if (array == null) { 1437 return; 1438 } 1439 String myNumber = excludeMyNumber ? mTelephonyManager.getLine1Number() : null; 1440 for (EncodedStringValue v : array) { 1441 if (v != null) { 1442 String number = v.getString(); 1443 if ((myNumber == null || !PhoneNumberUtils.compare(number, myNumber)) && 1444 !recipients.contains(number)) { 1445 // Only add numbers which aren't my own number. 1446 recipients.add(number); 1447 } 1448 } 1449 } 1450 } 1451 1452 /** 1453 * Move a PDU object from one location to another. 1454 * 1455 * @param from Specify the PDU object to be moved. 1456 * @param to The destination location, should be one of the following: 1457 * "content://mms/inbox", "content://mms/sent", 1458 * "content://mms/drafts", "content://mms/outbox", 1459 * "content://mms/trash". 1460 * @return New Uri of the moved PDU. 1461 * @throws MmsException Error occurred while moving the message. 1462 */ 1463 public Uri move(Uri from, Uri to) throws MmsException { 1464 // Check whether the 'msgId' has been assigned a valid value. 1465 long msgId = ContentUris.parseId(from); 1466 if (msgId == -1L) { 1467 throw new MmsException("Error! ID of the message: -1."); 1468 } 1469 1470 // Get corresponding int value of destination box. 1471 Integer msgBox = MESSAGE_BOX_MAP.get(to); 1472 if (msgBox == null) { 1473 throw new MmsException( 1474 "Bad destination, must be one of " 1475 + "content://mms/inbox, content://mms/sent, " 1476 + "content://mms/drafts, content://mms/outbox, " 1477 + "content://mms/temp."); 1478 } 1479 1480 ContentValues values = new ContentValues(1); 1481 values.put(Mms.MESSAGE_BOX, msgBox); 1482 SqliteWrapper.update(mContext, mContentResolver, from, values, null, null); 1483 return ContentUris.withAppendedId(to, msgId); 1484 } 1485 1486 /** 1487 * Wrap a byte[] into a String. 1488 */ 1489 public static String toIsoString(byte[] bytes) { 1490 try { 1491 return new String(bytes, CharacterSets.MIMENAME_ISO_8859_1); 1492 } catch (UnsupportedEncodingException e) { 1493 // Impossible to reach here! 1494 Log.e(TAG, "ISO_8859_1 must be supported!", e); 1495 return ""; 1496 } 1497 } 1498 1499 /** 1500 * Unpack a given String into a byte[]. 1501 */ 1502 public static byte[] getBytes(String data) { 1503 try { 1504 return data.getBytes(CharacterSets.MIMENAME_ISO_8859_1); 1505 } catch (UnsupportedEncodingException e) { 1506 // Impossible to reach here! 1507 Log.e(TAG, "ISO_8859_1 must be supported!", e); 1508 return new byte[0]; 1509 } 1510 } 1511 1512 /** 1513 * Remove all objects in the temporary path. 1514 */ 1515 public void release() { 1516 Uri uri = Uri.parse(TEMPORARY_DRM_OBJECT_URI); 1517 SqliteWrapper.delete(mContext, mContentResolver, uri, null, null); 1518 } 1519 1520 /** 1521 * Find all messages to be sent or downloaded before certain time. 1522 */ 1523 public Cursor getPendingMessages(long dueTime) { 1524 Uri.Builder uriBuilder = PendingMessages.CONTENT_URI.buildUpon(); 1525 uriBuilder.appendQueryParameter("protocol", "mms"); 1526 1527 String selection = PendingMessages.ERROR_TYPE + " < ?" 1528 + " AND " + PendingMessages.DUE_TIME + " <= ?"; 1529 1530 String[] selectionArgs = new String[] { 1531 String.valueOf(MmsSms.ERR_TYPE_GENERIC_PERMANENT), 1532 String.valueOf(dueTime) 1533 }; 1534 1535 return SqliteWrapper.query(mContext, mContentResolver, 1536 uriBuilder.build(), null, selection, selectionArgs, 1537 PendingMessages.DUE_TIME); 1538 } 1539} 1540