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