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