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