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