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